Update the "DBIC terminology" based on input from MST. Put in a note about "load_nam...
[catagits/Catalyst-Manual.git] / lib / Catalyst / Manual / Tutorial / MoreCatalystBasics.pod
index 5af6537..1c2086a 100644 (file)
@@ -140,20 +140,21 @@ very similar to Apache configuration files.  We will see how to use
 this feature of Catalyst during the authentication and authorization
 sections (Part 5 and Part 6).
 
-B<IMPORTANT NOTE:> If you are using a version of
-L<Catalyst::Devel|Catalyst::Devel> prior to version 1.06, you need to
-be aware that Catalyst changed from a default format of YAML to the
-more straightforward C<Config::General> format.  This tutorial use the
-newer C<myapp.conf> configuration file for C<Config::General> instead
-of C<myapp.yml> for YAML. However, Catalyst has long supported both
-formats and Catalyst will automatically use either C<myapp.conf> or
-C<myapp.yml> (or any other format supported by
-L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader> and
-L<Config::Any|Config::Any>).  If you are using a versions of
-Catalyst::Devel prior to 1.06, you can convert to the newer format by
-simply creating the C<myapp.yml> file manually and deleting
-C<myapp.yml>.  The default contents of C<myapp.conf> should only
-consist of one line: C<name MyApp>.
+B<IMPORTANT NOTE:> If you are using a version of 
+L<Catalyst::Devel|Catalyst::Devel> prior to version 1.06, be aware 
+that Catalyst changed the default format from YAML to the more 
+straightforward C<Config::General> style.  This tutorial uses the 
+newer C<myapp.conf> file for C<Config::General>. However, Catalyst 
+supports both formats and will automatically use either C<myapp.conf> 
+or C<myapp.yml> (or any other format supported by 
+L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader> and 
+L<Config::Any|Config::Any>).  If you are using a version of 
+Catalyst::Devel prior to 1.06, you can convert to the newer format by 
+simply creating the C<myapp.conf> file manually and deleting 
+C<myapp.yml>.  The default contents of the C<myapp.conf> you create 
+should only consist of one line: 
+
+    name MyApp
 
 B<TIP>: This script can be useful for converting between configuration
 formats:
@@ -161,10 +162,6 @@ formats:
     perl -Ilib -e 'use MyApp; use Config::General;
         Config::General->new->save_file("myapp.conf", MyApp->config);'
 
-B<NOTE:> The default C<myapp.conf> should look like:
-
-    name   MyApp
-
 =item *
 
 L<Catalyst::Plugin::Static::Simple|Catalyst::Plugin::Static::Simple>
@@ -174,7 +171,7 @@ as images and CSS files under the development server.
 
 =back
 
-For out application, we want to add one new plugin into the mix.  To 
+For our application, we want to add one new plugin into the mix.  To 
 do this, edit C<lib/MyApp.pm> (this file is generally referred to as 
 your I<application class>) and delete the line with:
 
@@ -190,6 +187,21 @@ Then replace it with:
             StackTrace
         /);
 
+B<Note:> Recent versions of C<Catalyst::Devel> have used a variety of 
+techniques to load these plugins/flags.  If you are following along in 
+Ubuntu 8.10, you should have C<Catalyst::Devel> v1.07 and see the 
+default code shown above.  If you are using v1.08, you should see the 
+following by default:
+
+    use Catalyst qw/-Debug
+                ConfigLoader
+                Static::Simple/;
+    ...
+    __PACKAGE__->setup();
+
+Don't let these variations confuse you -- they all accomplish the same 
+result.
+
 This tells Catalyst to start using one new plugin, 
 L<Catalyst::Plugin::StackTrace|Catalyst::Plugin::StackTrace>, to add a 
 stack trace to the standard Catalyst "debug screen" (the screen 
@@ -279,26 +291,89 @@ Context object is automatically passed to all Catalyst components.  It
 is used to pass information between components and provide access to 
 Catalyst and plugin functionality.
 
-B<Note:> Catalyst actions are regular Perl methods, but they make use 
-of Nicholas Clark's C<attributes> module (that's the "C<: Local>" next 
-to the C<sub list> in the code above) to provide additional 
-information to the Catalyst dispatcher logic.  Many newer Catalyst 
-applications are switching to the use of "Literal" C<:Path> actions 
-and C<Args> attribute in lieu of C<: Local> and C<: Private>.  For 
-example, C<sub any_method :Path :Args(0)> can be used instead of C<sub 
-index :Private> (because no path was supplied to C<Path> it matches 
-the "empty" URL in the namespace of that module... the same thing 
-C<sub index> would do) or C<sub list :Path('list') :Args(0)> could be 
-used instead of the C<sub list : Local> above (the C<list> argument to 
-C<Path> would make it match on the URL C<list> under C<books>, the 
-namespace of the current module).  See "Action Types" in 
-L<Catalyst::Manual::Intro|Catalyst::Manual::Intro> as well as Part 5 
-of this tutorial (Authentication) for additional information.  Another 
-popular but more advanced feature is C<Chained> actions that allow a 
-single URL to "chain together" multiple action method calls, each with 
-an appropriate number of arguments (see 
-L<Catalyst::DispatchType::Chained|Catalyst::DispatchType::Chained> for 
-details).
+Catalyst actions are regular Perl methods, but they make use of 
+attributes (the "C<: Local>" next to the "C<sub list>" in the code 
+above) to provide additional information to the Catalyst dispatcher 
+logic (note that the space between the colon and the attribute name is 
+optional... you will see attributes written both ways).  Most Catalyst 
+Controllers use one of five action types:
+
+=over 4
+
+=item *
+
+B<:Private> -- Use C<:Private> for methods that you want to make into 
+an action, but you do not want Catalyst to directly expose the action 
+to your users.  Catalyst will not map C<:Private> methods to a URI. 
+Use them for various sorts of "special" methods (the C<begin>, 
+C<auto>, etc. discussed below) or for methods you want to be able to 
+C<forward> or C<detach> to.  (If the method is a plain old "helper 
+method" that you don't want to be an action at all, then just define 
+the method without any attribute -- you can call it in your code, but 
+the Catalyst dispatcher will ignore it.)
+
+There are five types of "special" build-in C<:Private> actions: 
+C<begin>, C<end>, C<default>, C<index>, and C<auto>.
+
+=over 4
+
+=item *
+
+With C<begin>, C<end>, C<default>, C<index> private actions, only the
+most specific action of each type will be called.  For example, if you
+define a C<begin> action in your controller it will I<override> a
+C<begin> action in your application/root controller -- I<only> the
+action in your controller will be called.
+
+=item *
+
+Unlike the other actions where only a single method is called for each
+request, I<every> auto action along the chain of namespaces will be
+called.  Each C<auto> action will be called I<from the application/root
+controller down through the most specific class>.
+
+=back
+
+=item *
+
+B<:Path> -- C<:Path> actions let you map a method to an explicit URI 
+path.  For example, "C<:Path('list')>" in 
+C<lib/MyApp/Controller/Books.pm> would match on the URL 
+C<http://localhost:3000/books/list> but "C<:Path('/list')>" would match 
+on C<http://localhost:3000/list>.  You can use C<:Args()> to specify 
+how many arguments an action should except.  See 
+L<Catalyst::Manual::Intro/Action_types> for more information and a few 
+examples.
+
+=item *
+
+B<:Local> -- C<:Local> is merely a shorthand for 
+"C<:Path('_name_of_method_')>".  For example, these are equivalent:
+"C<sub create_book :Local {...}>" and 
+"C<sub create_book :Path('create_book') {...}>".
+
+=item *
+
+B<:Global> -- C<:Global> is merely a shorthand for 
+"C<:Path('/_name_of_method_')>".  For example, these are equivalent:
+"C<sub create_book :Global {...}>" and 
+"C<sub create_book :Path('/create_book') {...}>".
+
+=item *
+
+B<:Chained> -- Newer Catalyst applications tend to use the Chained 
+dispatch form of action types because of its power and flexibility.  
+It allows a series of controller methods to automatically be dispatched
+to service a single user request.  See 
+L<Catalyst::Manual::Tutorial::BasicCRUD|Catalyst::Manual::Tutorial::BasicCRUD> 
+and L<Catalyst::DispatchType::Chained|Catalyst::DispatchType::Chained> 
+for more information on chained actions.
+
+=back
+
+You should refer to L<Catalyst::Manual::Intro/Action_types> for 
+additional information and for coverage of some lesser-used action 
+types not discussed here (C<Regex> and C<LocalRegex>).
 
 
 =head1 CATALYST VIEWS
@@ -333,18 +408,20 @@ L<Catalyst::Helper::View::TTSite|Catalyst::Helper::View::TTSite>
 
 =back
 
-Both are similar, but C<TT> merely creates the C<lib/MyApp/View/TT.pm>
+Both helpers are similar. C<TT> creates the C<lib/MyApp/View/TT.pm>
 file and leaves the creation of any hierarchical template organization
 entirely up to you. (It also creates a C<t/view_TT.t> file for testing;
-test cases will be discussed in Part 8.) On the other hand, the
-C<TTSite> helper creates a modular and hierarchical view layout with
+test cases will be discussed in Part 8.) C<TTSite>, on the other hand, 
+creates a modular and hierarchical view layout with
 separate Template Toolkit (TT) files for common header and footer
 information, configuration values, a CSS stylesheet, and more.
 
-While TTSite is useful to bootstrap a project, most in the Catalyst 
-community recommend that it's easier to learn both Catalyst and 
-Tempalte Toolkit if you use the more basic TT approach.  Consequently, 
-this tutorial will use "plain old TT."
+While C<TTSite> was useful to bootstrap a project, its use is now
+deprecated and to be considered historical.  For most Catalyst
+applications it adds redundant functionality and structure; many in the
+Catalyst community recommend that it's easier to learn both Catalyst and
+Template Toolkit if you use the more basic C<TT> approach.
+Consequently, this tutorial will use "plain old TT."
 
 Enter the following command to enable the C<TT> style of view
 rendering for this tutorial:
@@ -376,16 +453,18 @@ And update it to match:
         TEMPLATE_EXTENSION => '.tt2',
         # Set the location for TT files
         INCLUDE_PATH => [
-                MyApp->path_to( 'root/src' ),
+                MyApp->path_to( 'root', 'src' ),
             ],
     );
 
 B<NOTE:> Make sure to add a comma after '.tt2' outside the single
 quote.
 
-This changes the default extenstion for Template Toolkit from '.tt' to
+This changes the default extension for Template Toolkit from '.tt' to
 '.tt2' and changes the base directory for your template files from
-C<root> to C<root/src>.
+C<root> to C<root/src>.  These changes from the default are done mostly
+to facilitate the application we're developing in this tutorial; as with
+most things Perl, there's more than one way to do it...
 
 
 =head2 Create a TT Template Page
@@ -550,19 +629,21 @@ required if you do a single SQL statement on the command line).  Use
 ".q" to exit from SQLite from the SQLite interactive mode and return to
 your OS command prompt.
 
+For using other databases, such as PostgreSQL or MySQL, see 
+L<Appendix 2|Catalyst::Manual::Tutorial::Appendices>.
 
 =head1 DATABASE ACCESS WITH C<DBIx::Class>
 
-Catalyst can be used with virtually any form of persistent datastore
-available via Perl.  For example,
-L<Catalyst::Model::DBI|Catalyst::Model::DBI> can be used to
-easily access databases through the traditional Perl C<DBI> interface.
-However, most Catalyst applications use some form of ORM technology to
-automatically create and save model objects as they are used.  Although
-Tony Bowden's L<Class::DBI|Class::DBI> has been a popular choice
-in the past, Matt Trout's L<DBIx::Class|DBIx::Class> (abbreviated
-as "DBIC") has rapidly emerged as the Perl-based ORM technology of choice.
-Most new Catalyst applications rely on DBIC, as will this tutorial.
+Catalyst can be used with virtually any form of persistent datastore 
+available via Perl.  For example, 
+L<Catalyst::Model::DBI|Catalyst::Model::DBI> can be used to easily 
+access databases through the traditional Perl C<DBI> interface. However, 
+most Catalyst applications use some form of ORM technology to 
+automatically create and save model objects as they are used.  Although 
+L<Class::DBI|Class::DBI> has been a popular choice in the past, Matt 
+Trout's L<DBIx::Class|DBIx::Class> (abbreviated as "DBIC") has rapidly 
+emerged as the Perl-based ORM technology of choice. Most new Catalyst 
+applications rely on DBIC, as will this tutorial.
 
 
 =head2 Create a Dynamic DBIC Model
@@ -580,15 +661,37 @@ starts:
     created "/home/me/MyApp/script/../t/model_DB.t"
 
 
+The C<script/myapp_create.pl> command breaks down like this:
+
+=over 4
+
+=item *
+
 C<DB> is the name of the model class to be created by the helper in 
-C<lib/MyApp/Model>.  C<DBIC::Schema> is the type of the model to 
-create. C<MyApp::Schema> is the name of the DBIC schema file written 
-to C<lib/MyApp/Schema.pm>.  Because we specified C<create=dynamic> to 
-the helper, it use 
+C<lib/MyApp/Model>.
+
+=item *
+
+C<DBIC::Schema> is the type of the model to create.
+
+=item *
+
+C<MyApp::Schema> is the name of the DBIC schema file written to
+C<lib/MyApp/Schema.pm>.
+
+=item *
+
+Because we specified C<create=dynamic> to the helper, it use 
 L<DBIx::Class::Schema::Loader|DBIx::Class::Schema::Loader> to 
 dynamically load the schema information from the database every time 
-the application starts. And finally, C<dbi:SQLite:myapp.db> is the 
-standard DBI connect string for use with SQLite.
+the application starts.
+
+=item *
+
+And finally, C<dbi:SQLite:myapp.db> is the standard DBI connect string 
+for use with SQLite.
+
+=back
 
 B<NOTE:> Although the C<create=dynamic> option to the DBIC helper
 makes for a nifty demonstration, is only really suitable for very
@@ -598,8 +701,8 @@ use the C<create=static> option that we switch to below.
 
 =head1 ENABLE THE MODEL IN THE CONTROLLER
 
-Open C<lib/MyApp/Controller/Books.pm> and uncomment the model code we
-left disabled earlier (uncomment the line containing
+Open C<lib/MyApp/Controller/Books.pm> and un-comment the model code we
+left disabled earlier (un-comment the line containing
 C<[$c-E<gt>model('DB::Books')-E<gt>all]> and delete the next 2 lines):
 
     =head2 list
@@ -624,16 +727,34 @@ C<[$c-E<gt>model('DB::Books')-E<gt>all]> and delete the next 2 lines):
         $c->stash->{template} = 'books/list.tt2';
     }
 
-B<TIP>: You may see the C<$c-E<gt>model('DB::Book')> uncommented above
-written as C<$c-E<gt>model('DB')-E<gt>resultset('Book')>.  The two
-are equivalent.
+B<TIP>: You may see the C<$c-E<gt>model('DB::Books')> un-commented 
+above written as C<$c-E<gt>model('DB')-E<gt>resultset('Books')>.  The 
+two are equivalent.  Either way, C<$c-E<gt>model> returns a 
+L<DBIx::Class::ResultSet|DBIx::Class::ResultSet> which handles queries 
+against the database and iterating over the set of results that are 
+returned.
+
+We are using the C<-E<gt>all> to fetch all of the books.  DBIC 
+supports a wide variety of more advanced operations to easily do 
+things like filtering and sorting the results.  For example, the 
+following could be used to sort the results by descending title:
+
+    $c->model('DB::Books')->search({}, {order_by => 'title DESC'});
+
+Some other examples are provided in 
+L<DBIx::Class::Manual::Cookbook/Complex WHERE clauses>, with 
+additional information found at L<DBIx::Class::ResultSet/search>, 
+L<DBIx::Class::Manual::FAQ/Searching>, 
+L<DBIx::Class::Manual::Intro|DBIx::Class::Manual::Intro> 
+and L<Catalyst::Model::DBIC::Schema|Catalyst::Model::DBIC::Schema>.
 
 
 =head2 Test Run The Application
 
-First, let's enable an environment variable option that causes
-DBIx::Class to dump the SQL statements it's using to access the database
-(this option can provide extremely helpful troubleshooting information):
+First, let's enable an environment variable that causes DBIx::Class to 
+dump the SQL statements usedß to access the database.  This is a 
+helpful trick when you are trying to debug your database-oriented 
+code:
 
     $ export DBIC_TRACE=1
 
@@ -733,7 +854,7 @@ Next, to view the book list, change the URL in your browser to
 L<http://localhost:3000/books/list>. You should get a list of the five
 books loaded by the C<myapp01.sql> script above without any formatting.
 The rating for each book should appear on each row, but the "Author(s)"
-column will sitll be blank (we will fill that in later).
+column will still be blank (we will fill that in later).
 
 Also notice in the output of the C<script/myapp_server.pl> that DBIC
 used the following SQL to retrieve the data:
@@ -769,7 +890,7 @@ Edit C<lib/MyApp/View/TT.pm> and change it to match the following:
         TEMPLATE_EXTENSION => '.tt2',
         # Set the location for TT files
         INCLUDE_PATH => [
-                MyApp->path_to( 'root/src' ),
+                MyApp->path_to( 'root', 'src' ),
             ],
         # Set to 1 for detailed timer stats in your HTML as comments
         TIMER              => 0,
@@ -821,7 +942,7 @@ For the tutorial, open C<root/src/wrapper.tt2> and input the following:
     </div><!-- end bodyblock -->
     
     <div id="footer">Copyright (c) your name goes here</div>
-    </div><!-- end outter -->
+    </div><!-- end outer -->
     
     </body>
     </html>
@@ -959,7 +1080,7 @@ L<DBIx::Class::Schema::Loader|DBIx::Class::Schema::Loader> as its base
 class (L<DBIx::Class::Schema::Loader|DBIx::Class::Schema::Loader> is 
 only being used by the helper to load the schema once and then create 
 the static files for us) and C<Schema.pm> only contains a call to the 
-C<load_classes> method.  You will also find that C<lib/MyApp/Schema> 
+C<load_classes> method.  You will also find that C<lib/MyApp> 
 contains a C<Schema> subdirectory, with one file inside this directory 
 for each of the tables in our simple database (C<Authors.pm>, 
 C<BookAuthors.pm>, and C<Books.pm>).  These three files were created 
@@ -974,16 +1095,46 @@ changes below that point in the file, you can regenerate the
 automatically created information at the top of each file should your 
 database structure get updated.
 
-Also note the "flow" of the model information across the various files
-and directories.  Catalyst will initially load the model from
-C<lib/MyApp/Model/DB.pm>.  This file contains a reference to
-C<lib/MyApp/Schema.pm>, so that file is loaded next.  Finally,
-the call to C<load_classes> in C<Schema.pm> will load each of the
-table-specific "results source" files from the C<lib/MyApp/Schema>
-subdirectory.  These three table-specific DBIC schema files will then be
-used to create three table-specific Catalyst models every time the
-application starts (you can see these three model files listed in
-the debug output generated when you launch the application).
+Also note the "flow" of the model information across the various files 
+and directories.  Catalyst will initially load the model from 
+C<lib/MyApp/Model/DB.pm>.  This file contains a reference to 
+C<lib/MyApp/Schema.pm>, so that file is loaded next.  Finally, the 
+call to C<load_classes> in C<Schema.pm> will load each of the "result 
+class" files from the C<lib/MyApp/Schema> subdirectory.  The end 
+result is that Catalyst will dynamically create three table-specific 
+Catalyst models every time the application starts (you can see these 
+three model files listed in the debug output generated when you launch 
+the application).
+
+B<NOTE:> The version of 
+L<Catalyst::Model::DBIC::Schema|Catalyst::Model::DBIC::Schema> in 
+Ubuntu 8.10 uses the older DBIC C<load_classes> vs. the newer 
+C<load_namspaces> technique.  For new applications, please try to use 
+C<load_namespaces> since it more easily supports a very useful DBIC
+technique called "ResultSet Classes."  This tutorial expects to migrate to 
+C<load_namespaces> when the next release of Ubuntu comes out.
+
+If you wish to try C<load_namespaces> now, you can manually do the
+equivalent of the C<create=static> operation outside of the Catalyst
+helper:
+
+    perl -MDBIx::Class::Schema::Loader=make_schema_at,dump_to_dir:./lib -e \
+        'make_schema_at("MyApp::Schema", { debug => 1, use_namespaces => 1, \
+        components => ["InflateColumn::DateTime"] },["dbi:SQLite:myapp.db", "", "" ])'
+
+And then use the helper to only create the Catalyst model class:
+
+    script/myapp_create.pl model DB DBIC::Schema MyApp::Schema dbi:SQLite:myapp.db
+
+B<However>, it is important to note that C<load_namespaces> will look 
+for your C<Books.pm>, <Authors.pm>, etc. files in 
+C<lib/MyApp/Schema/Result> (it adds the subdirection "Result" so that 
+there can also be a "ResultSet" directory next to it in the 
+hierarchy).  Therefore, if you switch to C<load_namespaces>, you will 
+need to modify the path to these "result class" files throughout the 
+rest of the tutorial.  Our recommendation for now would be to complete 
+the tutorial using C<load_classes> and the try converting to 
+C<load_namespaces> after you are done.
 
 
 =head2 Updating the Generated DBIC Schema Files
@@ -1001,7 +1152,7 @@ comment:
     #   args:
     #     1) Name of relationship, DBIC will create accessor with this name
     #     2) Name of the model class referenced by this relationship
-    #     3) Column name in *foreign* table
+    #     3) Column name in *foreign* table (aka, foreign key in peer table)
     __PACKAGE__->has_many(book_authors => 'MyApp::Schema::BookAuthors', 'book_id');
     
     # many_to_many():
@@ -1043,7 +1194,7 @@ below the C<# DO NOT MODIFY THIS OR ANYTHING ABOVE!> comment):
     #   args:
     #     1) Name of relationship, DBIC will create accessor with this name
     #     2) Name of the model class referenced by this relationship
-    #     3) Column name in *foreign* table
+    #     3) Column name in *foreign* table (aka, foreign key in peer table)
     __PACKAGE__->has_many(book_author => 'MyApp::Schema::BookAuthors', 'author_id');
     
     # many_to_many():
@@ -1088,11 +1239,13 @@ Make sure that the application loads correctly and that you see the
 three dynamically created model class (one for each of the
 table-specific schema classes we created).
 
-Then hit the URL L<http://localhost:3000/books/list> and be sure that
-the book list is displayed.
+Then hit the URL L<http://localhost:3000/books/list> and be sure that 
+the book list is displayed via the relationships established above. You 
+can leave the development server running for the next step if you wish.
 
-You can leave the development server running for the next step if you
-wish.
+B<Note:> You will not see the authors yet because the view does not yet 
+use the new relations. Read on to the next section where we update the 
+template to do that.
 
 
 =head1 UPDATING THE VIEW
@@ -1134,15 +1287,23 @@ enabled, you should also now see five more C<SELECT> statements in the
 debug output (one for each book as the authors are being retrieved by
 DBIC).
 
-Also note that we are using "| html", a type of TT filter, to escape
-characters such as E<lt> and E<gt> to &lt; and &gt; and avoid various
-types of dangerous hacks against your application.  In a real
-application, you would probably want to put "| html" at the end of
-every field where a user has control over the information that can
-appear in that field (and can therefore inject markup or code if you
-don't "neutralize" those fields).  In addition to "| html", Template
-Toolkit has a variety of other useful filters that can found in the
-documentation for L<Template::Filters|Template::Filters>.
+    SELECT me.id, me.title, me.rating FROM books me:
+    SELECT me.book_id, me.author_id FROM book_authors me WHERE ( me.book_id = ? ): '1'
+    SELECT me.book_id, me.author_id FROM book_authors me WHERE ( me.book_id = ? ): '2'
+    SELECT me.book_id, me.author_id FROM book_authors me WHERE ( me.book_id = ? ): '3'
+    SELECT me.book_id, me.author_id FROM book_authors me WHERE ( me.book_id = ? ): '4'
+    SELECT me.book_id, me.author_id FROM book_authors me WHERE ( me.book_id = ? ): '5'
+
+Also note in C<root/src/books/list.tt2> that we are using "| html", a 
+type of TT filter, to escape characters such as E<lt> and E<gt> to &lt; 
+and &gt; and avoid various types of dangerous hacks against your 
+application.  In a real application, you would probably want to put 
+"| html" at the end of every field where a user has control over the 
+information that can appear in that field (and can therefore inject 
+markup or code if you don't "neutralize" those fields).  In addition to 
+"| html", Template Toolkit has a variety of other useful filters that 
+can found in the documentation for 
+L<Template::Filters|Template::Filters>.
 
 
 =head1 RUNNING THE APPLICATION FROM THE COMMAND LINE
@@ -1285,7 +1446,7 @@ Kennedy Clark, C<hkclark@gmail.com>
 
 Please report any errors, issues or suggestions to the author.  The
 most recent version of the Catalyst Tutorial can be found at
-L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Manual/lib/Catalyst/Manual/Tutorial/>.
+L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
 
 Copyright 2006-2008, Kennedy Clark, under Creative Commons License
 (L<http://creativecommons.org/licenses/by-sa/3.0/us/>).