Merge from depluralization branch
Kennedy Clark [Sun, 24 May 2009 22:06:59 +0000 (22:06 +0000)]
lib/Catalyst/Manual/Tutorial/AdvancedCRUD/FormFu.pod
lib/Catalyst/Manual/Tutorial/Authentication.pod
lib/Catalyst/Manual/Tutorial/Authorization.pod
lib/Catalyst/Manual/Tutorial/BasicCRUD.pod
lib/Catalyst/Manual/Tutorial/CatalystBasics.pod
lib/Catalyst/Manual/Tutorial/Debugging.pod
lib/Catalyst/Manual/Tutorial/MoreCatalystBasics.pod
lib/Catalyst/Manual/Tutorial/Testing.pod

index cbcf259..1c7176d 100644 (file)
@@ -132,7 +132,7 @@ following method:
         # is shorthand for "$form->submitted && !$form->has_errors"
         if ($form->submitted_and_valid) {
             # Create a new book
-            my $book = $c->model('DB::Books')->new_result({});
+            my $book = $c->model('DB::Book')->new_result({});
             # Save the form data for the book
             $form->model->update($book);
             # Set a status message for the user
@@ -142,7 +142,7 @@ following method:
             $c->detach;
         } else {
             # Get the authors from the DB
-            my @author_objs = $c->model("DB::Authors")->all();
+            my @author_objs = $c->model("DB::Author")->all();
             # Create an array of arrayrefs where each arrayref is an author
             my @authors;
             foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
@@ -270,17 +270,21 @@ running) and restart it:
 
 Login as C<test01> (password: mypass).  Once at the Book List page,
 click the new HTML::FormFu "Create" link at the bottom to display the
-form.  Fill in the following values: Title = "Internetworking with
-TCP/IP Vol. II", Rating = "4", and Author = "Comer".  Click Submit,
-and you will be returned to the Book List page with a "Book created"
-status message displayed.
+form.  Fill in the following values:
 
-Also note that this implementation allows you to can create books with 
+    Title  = "Internetworking with TCP/IP Vol. II"
+    Rating = "4"
+    Author = "Comer"
+    
+Click the Submit button, and you will be returned to the Book List page
+with a "Book created" status message displayed.
+
+Also note that this implementation allows you to create books with any
 bogus information.  Although we have constrained the authors with the 
 drop-down list (note that this isn't bulletproof because we still have 
 not prevented a user from "hacking" the form to specify other values), 
 there are no restrictions on items such as the length of the title (for 
-example, you can create a one-letter title) and value for the rating 
+example, you can create a one-letter title) and the value of the rating 
 (you can use any number you want, and even non-numeric values with 
 SQLite).  The next section will address this concern.
 
@@ -478,7 +482,7 @@ bottom:
             $c->detach;
         } else {
             # Get the authors from the DB
-            my @author_objs = $c->model("DB::Authors")->all();
+            my @author_objs = $c->model("DB::Author")->all();
             # Create an array of arrayrefs where each arrayref is an author
             my @authors;
             foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
index 2d194c7..0d7e7ab 100644 (file)
@@ -82,9 +82,9 @@ authorization section, Chapter 6).  Create a new SQL script file by opening
 C<myapp02.sql> in your editor and insert:
 
     --
-    -- Add users and roles tables, along with a many-to-many join table
+    -- Add user and role tables, along with a many-to-many join table
     --
-    CREATE TABLE users (
+    CREATE TABLE user (
             id            INTEGER PRIMARY KEY,
             username      TEXT,
             password      TEXT,
@@ -93,11 +93,11 @@ C<myapp02.sql> in your editor and insert:
             last_name     TEXT,
             active        INTEGER
     );
-    CREATE TABLE roles (
+    CREATE TABLE role (
             id   INTEGER PRIMARY KEY,
             role TEXT
     );
-    CREATE TABLE user_roles (
+    CREATE TABLE user_role (
             user_id INTEGER,
             role_id INTEGER,
             PRIMARY KEY (user_id, role_id)
@@ -105,21 +105,20 @@ C<myapp02.sql> in your editor and insert:
     --
     -- Load up some initial test data
     --
-    INSERT INTO users VALUES (1, 'test01', 'mypass', 't01@na.com', 'Joe',  'Blow', 1);
-    INSERT INTO users VALUES (2, 'test02', 'mypass', 't02@na.com', 'Jane', 'Doe',  1);
-    INSERT INTO users VALUES (3, 'test03', 'mypass', 't03@na.com', 'No',   'Go',   0);
-    INSERT INTO roles VALUES (1, 'user');
-    INSERT INTO roles VALUES (2, 'admin');
-    INSERT INTO user_roles VALUES (1, 1);
-    INSERT INTO user_roles VALUES (1, 2);
-    INSERT INTO user_roles VALUES (2, 1);
-    INSERT INTO user_roles VALUES (3, 1);
+    INSERT INTO user VALUES (1, 'test01', 'mypass', 't01@na.com', 'Joe',  'Blow', 1);
+    INSERT INTO user VALUES (2, 'test02', 'mypass', 't02@na.com', 'Jane', 'Doe',  1);
+    INSERT INTO user VALUES (3, 'test03', 'mypass', 't03@na.com', 'No',   'Go',   0);
+    INSERT INTO role VALUES (1, 'user');
+    INSERT INTO role VALUES (2, 'admin');
+    INSERT INTO user_role VALUES (1, 1);
+    INSERT INTO user_role VALUES (1, 2);
+    INSERT INTO user_role VALUES (2, 1);
+    INSERT INTO user_role VALUES (3, 1);
 
 Then load this into the C<myapp.db> database with the following command:
 
     $ sqlite3 myapp.db < myapp02.sql
 
-
 =head2 Add User and Role Information to DBIC Schema
 
 Although we could manually edit the DBIC schema information to include
@@ -135,7 +134,7 @@ option on the DBIC model helper to do most of the work for us:
      exists "/root/dev/MyApp/script/../lib/MyApp/Model/DB.pm"
     $
     $ ls lib/MyApp/Schema/Result
-    Authors.pm  BookAuthors.pm  Books.pm  Roles.pm  UserRoles.pm  Users.pm
+    Author.pm  BookAuthor.pm  Book.pm  Role.pm  User.pm  UserRole.pm
 
 Notice how the helper has added three new table-specific result source
 files to the C<lib/MyApp/Schema/Result> directory.  And, more
@@ -144,12 +143,12 @@ files, those changes would have only been written above the C<# DO NOT
 MODIFY THIS OR ANYTHING ABOVE!> comment and your hand-edited
 enhancements would have been preserved.
 
-Speaking of "hand-edit ted enhancements," we should now add
+Speaking of "hand-editted enhancements," we should now add
 relationship information to the three new result source files.  Edit
 each of these files and add the following information between the C<#
 DO NOT MODIFY THIS OR ANYTHING ABOVE!> comment and the closing C<1;>:
 
-C<lib/MyApp/Schema/Result/Users.pm>:
+C<lib/MyApp/Schema/Result/User.pm>:
 
     #
     # Set relationships:
@@ -160,7 +159,7 @@ C<lib/MyApp/Schema/Result/Users.pm>:
     #     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 (aka, foreign key in peer table)
-    __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::Result::UserRoles', 'user_id');
+    __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::Result::UserRole', 'user_id');
     
     # many_to_many():
     #   args:
@@ -171,7 +170,7 @@ C<lib/MyApp/Schema/Result/Users.pm>:
     __PACKAGE__->many_to_many(roles => 'map_user_role', 'role');
 
 
-C<lib/MyApp/Schema/Result/Roles.pm>:
+C<lib/MyApp/Schema/Result/Role.pm>:
 
     #
     # Set relationships:
@@ -182,10 +181,10 @@ C<lib/MyApp/Schema/Result/Roles.pm>:
     #     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 (aka, foreign key in peer table)
-    __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::Result::UserRoles', 'role_id');
+    __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::Result::UserRole', 'role_id');
 
 
-C<lib/MyApp/Schema/Result/UserRoles.pm>:
+C<lib/MyApp/Schema/Result/UserRole.pm>:
 
     #
     # Set relationships:
@@ -196,18 +195,17 @@ C<lib/MyApp/Schema/Result/UserRoles.pm>:
     #     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 *this* table
-    __PACKAGE__->belongs_to(user => 'MyApp::Schema::Result::Users', 'user_id');
+    __PACKAGE__->belongs_to(user => 'MyApp::Schema::Result::User', 'user_id');
     
     # belongs_to():
     #   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 *this* table
-    __PACKAGE__->belongs_to(role => 'MyApp::Schema::Result::Roles', 'role_id');
-
+    __PACKAGE__->belongs_to(role => 'MyApp::Schema::Result::Role', 'role_id');
 
 The code for these three sets of updates is obviously very similar to
-the edits we made to the C<Books>, C<Authors>, and C<BookAuthors>
+the edits we made to the C<Book>, C<Author>, and C<BookAuthor>
 classes created in Chapter 3.
 
 Note that we do not need to make any change to the
@@ -236,11 +234,11 @@ Look for the three new model objects in the startup debug output:
     | MyApp::Controller::Root                                           | instance |
     | MyApp::Model::DB                                                  | instance |
     | MyApp::Model::DB::Author                                          | class    |
-    | MyApp::Model::DB::Books                                           | class    |
-    | MyApp::Model::DB::BookAuthors                                     | class    |
-    | MyApp::Model::DB::Roles                                           | class    |
-    | MyApp::Model::DB::Users                                           | class    |
-    | MyApp::Model::DB::UserRoles                                       | class    |
+    | MyApp::Model::DB::Book                                            | class    |
+    | MyApp::Model::DB::BookAuthor                                      | class    |
+    | MyApp::Model::DB::Role                                            | class    |
+    | MyApp::Model::DB::User                                            | class    |
+    | MyApp::Model::DB::UserRole                                        | class    |
     | MyApp::View::TT                                                   | instance |
     '-------------------------------------------------------------------+----------'
     ...
@@ -256,17 +254,17 @@ C<StackTrace> is new):
 
     # Load plugins
     use Catalyst qw/-Debug
-                ConfigLoader
-                Static::Simple
+                    ConfigLoader
+                    Static::Simple
     
-                StackTrace
+                    StackTrace
     
-                Authentication
+                    Authentication
     
-                Session
-                Session::Store::FastMmap
-                Session::State::Cookie
-                /;
+                    Session
+                    Session::Store::FastMmap
+                    Session::State::Cookie
+                    /;
 
 B<Note:> As discussed in MoreCatalystBasics, different versions of
 C<Catalyst::Devel> have used a variety of methods to load the plugins.
@@ -283,6 +281,16 @@ Authentication::Store or Authentication::Credential plugin. Instead,
 indicate the Store and Credential you want to use in your application
 configuration (see below).
 
+Make sure you include the additional plugins as new dependencies in
+the Makefile.PL file something like this:
+
+    requires (
+        'Catalyst::Plugin::Authentication' => '0',
+        'Catalyst::Plugin::Session' => '0',
+        'Catalyst::Plugin::Session::Store::FastMmap' => '0',
+        'Catalyst::Plugin::Session::State::Cookie' => '0',
+    );
+
 Note that there are several options for
 L<Session::Store|Catalyst::Plugin::Session::Store>
 (L<Session::Store::FastMmap|Catalyst::Plugin::Session::Store::FastMmap>
@@ -296,7 +304,7 @@ backed session store).
 
 =head2 Configure Authentication
 
-There are a variety of way to provide configuration information to
+There are a variety of ways to provide configuration information to
 L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>.
 Here we will use 
 L<Catalyst::Authentication::Realm::SimpleDB|Catalyst::Authentication::Realm::SimpleDB>
@@ -308,7 +316,7 @@ C<__PACKAGE__-E<gt>setup();>:
     __PACKAGE__->config->{'Plugin::Authentication'} = {
             default => {
                 class           => 'SimpleDB',
-                user_model      => 'DB::Users',
+                user_model      => 'DB::User',
                 password_type   => 'clear',
             },
         };
@@ -328,7 +336,7 @@ to the following code:
         use_session 1
         <default>
             password_type self_check
-            user_model    DB::Users
+            user_model    DB::User
             class         SimpleDB
         </default>
     </Plugin::Authentication>
@@ -400,6 +408,9 @@ and update the definition of C<sub index> to match:
         $c->stash->{template} = 'login.tt2';
     }
 
+Be sure to remove the C<$c-E<gt>response-E<gt>body('Matched MyApp::Controller::Login in Login.');>
+line of the C<sub index>.
+
 This controller fetches the C<username> and C<password> values from the
 login form and attempts to authenticate the user.  If successful, it
 redirects the user to the book list page.  If the login fails, the user
@@ -661,7 +672,7 @@ argument:
 
 If you then open one of the Result Classes, you will see that it 
 includes EncodedColumn in the C<load_components> line.  Take a look at 
-C<lib/MyApp/Schema/Result/Users.pm> since that's the main class where we
+C<lib/MyApp/Schema/Result/User.pm> since that's the main class where we
 want to use hashed and salted passwords:
 
     __PACKAGE__->load_components("InflateColumn::DateTime", "TimeStamp", "EncodedColumn", "Core");
@@ -669,7 +680,7 @@ want to use hashed and salted passwords:
 
 =head2 Modify the "password" Column to Use EncodedColumn
 
-Open the file C<lib/MyApp/Schema/Result/Users.pm> and enter the following
+Open the file C<lib/MyApp/Schema/Result/User.pm> and enter the following
 text below the "# DO NOT MODIFY THIS OR ANYTHING ABOVE!" line but above
 the closing "1;":
 
@@ -720,7 +731,7 @@ C<set_hashed_passwords.pl> in your editor and enter the following text:
     
     my $schema = MyApp::Schema->connect('dbi:SQLite:myapp.db');
     
-    my @users = $schema->resultset('Users')->all;
+    my @users = $schema->resultset('User')->all;
     
     foreach my $user (@users) {
         $user->password('mypass');
@@ -741,7 +752,7 @@ C<lib> directory for our C<MyApp::Schema> model.
 
 Then dump the users table to verify that it worked:
 
-    $ sqlite3 myapp.db "select * from users"
+    $ sqlite3 myapp.db "select * from user"
     1|test01|38d3974fa9e9263099f7bc2574284b2f55473a9bM=fwpX2NR8|t01@na.com|Joe|Blow|1
     2|test02|6ed8586587e53e0d7509b1cfed5df08feadc68cbMJlnPyPt0I|t02@na.com|Jane|Doe|1
     3|test03|af929a151340c6aed4d54d7e2651795d1ad2e2f7UW8dHoGv9z|t03@na.com|No|Go|0
@@ -761,7 +772,7 @@ is to the C<password_type> field):
     __PACKAGE__->config->{'Plugin::Authentication'} = {
             default => {
                 class           => 'SimpleDB',
-                user_model      => 'DB::Users',
+                user_model      => 'DB::User',
                 password_type   => 'self_check',
             },
         };
index 7eb2a4e..fce6161 100644 (file)
@@ -80,24 +80,32 @@ Edit C<lib/MyApp.pm> and add C<Authorization::Roles> to the list:
 
     # Load plugins
     use Catalyst qw/-Debug
-                ConfigLoader
-                Static::Simple
+                    ConfigLoader
+                    Static::Simple
                 
-                StackTrace
+                    StackTrace
                 
-                Authentication
-                Authorization::Roles
+                    Authentication
+                    Authorization::Roles
         
-                Session
-                Session::Store::FastMmap
-                Session::State::Cookie
-                /;
+                    Session
+                    Session::Store::FastMmap
+                    Session::State::Cookie
+                    /;
 
 B<Note:> As discussed in MoreCatalystBasics, different versions of 
 C<Catalyst::Devel> have used a variety of methods to load the plugins. 
 You can put the plugins in the C<use Catalyst> statement if you 
 prefer.
 
+Once again (remain sharp, by now you should be getting the hang of things)
+include this additional plugin as a new dependency in the Makefile.PL file
+like this:
+
+    requires (
+        ...
+        'Catalyst::Plugin::Authorization::Roles' => '0',
+    );
 
 =head2 Add Role-Specific Logic to the "Book List" Template
 
@@ -109,7 +117,7 @@ lines to the bottom of the file:
     
     <ul>
       [% # Dump list of roles -%]
-      [% FOR role = c.user.roles %]<li>[% role %]</li>[% END %]
+      [% FOR role = c.user.role %]<li>[% role %]</li>[% END %]
     </ul>
     
     <p>
@@ -160,16 +168,16 @@ updating C<url_create> to match the following code:
         if ($c->check_user_roles('admin')) {
             # Call create() on the book model object. Pass the table
             # columns/field values we want to set as hash values
-            my $book = $c->model('DB::Books')->create({
+            my $book = $c->model('DB::Book')->create({
                     title   => $title,
                     rating  => $rating
                 });
     
             # Add a record to the join table for this book, mapping to
             # appropriate author
-            $book->add_to_book_authors({author_id => $author_id});
+            $book->add_to_book_author({author_id => $author_id});
             # Note: Above is a shortcut for this:
-            # $book->create_related('book_authors', {author_id => $author_id});
+            # $book->create_related('book_author', {author_id => $author_id});
     
             # Assign the Book object to the stash for display in the view
             $c->stash->{book} = $book;
@@ -236,7 +244,7 @@ your controllers and views be an "thin" as possible, with all of the
 
 For example, let's add a method to our C<Books.pm> Result Class to 
 check if a user is allowed to delete a book.  Open 
-C<lib/MyApp/Schema/Result/Books.pm> and add the following method 
+C<lib/MyApp/Schema/Result/Book.pm> and add the following method 
 (be sure to add it below the "C<DO NOT MODIFY ...>" line):
 
     =head2 delete_allowed_by
@@ -254,7 +262,7 @@ C<lib/MyApp/Schema/Result/Books.pm> and add the following method
 
 Here we call a C<has_role> method on our user object, so we should add 
 this method to our Result Class.  Open 
-C<lib/MyApp/Schema/Result/Users.pm> and add the following method below 
+C<lib/MyApp/Schema/Result/User.pm> and add the following method below 
 the "C<DO NOT MODIFY ...>" line:
 
     =head 2 has_role
index 3eb6830..463e8d5 100644 (file)
@@ -105,16 +105,16 @@ Edit C<lib/MyApp/Controller/Books.pm> and enter the following method:
 
         # Call create() on the book model object. Pass the table
         # columns/field values we want to set as hash values
-        my $book = $c->model('DB::Books')->create({
+        my $book = $c->model('DB::Book')->create({
                 title  => $title,
                 rating => $rating
             });
 
         # Add a record to the join table for this book, mapping to
         # appropriate author
-        $book->add_to_book_authors({author_id => $author_id});
+        $book->add_to_book_author({author_id => $author_id});
         # Note: Above is a shortcut for this:
-        # $book->create_related('book_authors', {author_id => $author_id});
+        # $book->create_related('book_author', {author_id => $author_id});
 
         # Assign the Book object to the stash for display in the view
         $c->stash->{book} = $book;
@@ -127,7 +127,7 @@ Notice that Catalyst takes "extra slash-separated information" from the
 URL and passes it as arguments in C<@_>.  The C<url_create> action then
 uses a simple call to the DBIC C<create> method to add the requested
 information to the database (with a separate call to
-C<add_to_book_authors> to update the join table).  As do virtually all
+C<add_to_book_author> to update the join table).  As do virtually all
 controller methods (at least the ones that directly handle user input),
 it then sets the template that should handle this request.
 
@@ -153,8 +153,8 @@ Edit C<root/src/books/create_done.tt2> and then enter:
 
     [% # Output the last name of the first author.  This is complicated by an    -%]
     [% # issue in TT 2.15 where blessed hash objects are not handled right.      -%]
-    [% # First, fetch 'book.authors' from the DB once.                           -%]
-    [% authors = book.authors %]
+    [% # First, fetch 'book.author' from the DB once.                           -%]
+    [% authors = book.author %]
     [% # Now use IF statements to test if 'authors.first' is "working". If so,   -%]
     [% # we use it.  Otherwise we use a hack that seems to keep TT 2.15 happy.   -%]
     by '[% authors.first.last_name IF authors.first;
@@ -208,11 +208,8 @@ object as it was returned by DBIC.  You should also see the following
 DBIC debug messages displayed in the development server log messages
 if you have DBIC_TRACE set:
 
-    INSERT INTO books (rating, title) VALUES (?, ?): `5', `TCPIP_Illustrated_Vol-2'
-    INSERT INTO book_authors (author_id, book_id) VALUES (?, ?): `4', `6'
-    SELECT author.id, author.first_name, author.last_name
-        FROM book_authors me  JOIN authors author
-        ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '6'
+    INSERT INTO book (rating, title) VALUES (?, ?): `5', `TCPIP_Illustrated_Vol-2'
+    INSERT INTO book_author (author_id, book_id) VALUES (?, ?): `4', `6'
 
 The C<INSERT> statements are obviously adding the book and linking it to
 the existing record for Richard Stevens.  The C<SELECT> statement results
@@ -220,7 +217,12 @@ from DBIC automatically fetching the book for the C<Dumper.dump(book)>.
 
 If you then click the "Return to list" link, you should find that
 there are now six books shown (if necessary, Shift+Reload or
-Ctrl+Reload your browser at the C</books/list> page).
+Ctrl+Reload your browser at the C</books/list> page).  You should now see
+the following six DBIC debug messages displayed for N=1-6:
+
+    SELECT author.id, author.first_name, author.last_name \
+        FROM book_author me  JOIN author author \
+        ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): 'N'
 
 
 =head1 CONVERT TO A CHAINED ACTION
@@ -400,7 +402,7 @@ method:
         my ($self, $c) = @_;
 
         # Store the ResultSet in stash so it's available for other methods
-        $c->stash->{resultset} = $c->model('DB::Books');
+        $c->stash->{resultset} = $c->model('DB::Book');
 
         # Print a message to the debug log
         $c->log->debug('*** INSIDE BASE METHOD ***');
@@ -516,12 +518,12 @@ save the form information to the database:
         my $author_id = $c->request->params->{author_id} || '1';
 
         # Create the book
-        my $book = $c->model('DB::Books')->create({
+        my $book = $c->model('DB::Book')->create({
                 title   => $title,
                 rating  => $rating,
             });
         # Handle relationship with author
-        $book->add_to_book_authors({author_id => $author_id});
+        $book->add_to_book_author({author_id => $author_id});
 
         # Store new model object in stash
         $c->stash->{book} = $book;
@@ -602,14 +604,15 @@ and 2) the four lines for the Delete link near the bottom):
           [% # authors into the list. Note that the 'push' TT vmethod doesn't return -%]
           [% # a value, so nothing will be printed here.  But, if you have something -%]
           [% # in TT that does return a value and you don't want it printed, you can -%]
-          [% # 1) assign it to a bogus value, or                                     -%]
-          [% # 2) use the CALL keyword to call it and discard the return value.      -%]
+          [% # 1) assign it to a bogus value, or # 2) use the CALL keyword to        -%]
+          [% # call it and discard the return value.                                 -%]
           [% tt_authors = [ ];
-             tt_authors.push(author.last_name) FOREACH author = book.authors %]
+             tt_authors.push(author.last_name) FOREACH author = book.author %]
           [% # Now use a TT 'virtual method' to display the author count in parens   -%]
-          ([% tt_authors.size %])
+          [% # Note the use of the TT filter "| html" to escape dangerous characters -%]
+          ([% tt_authors.size | html %])
           [% # Use another TT vmethod to join & print the names & comma separators   -%]
-          [% tt_authors.join(', ') %]
+          [% tt_authors.join(', ') | html %]
         </td>
         <td>
           [% # Add a link to delete a book %]
@@ -729,7 +732,7 @@ following method:
         my ($self, $c) = @_;
 
         # Use the book object saved by 'object' and delete it along
-        # with related 'book_authors' entries
+        # with related 'book_author' entries
         $c->stash->{object}->delete;
 
         # Set a status message to be displayed at the top of the view
@@ -741,7 +744,7 @@ following method:
 
 This method first deletes the book object saved by the C<object> method.
 However, it also removes the corresponding entry from the
-C<book_authors> table with a cascading delete.
+C<book_author> table with a cascading delete.
 
 Then, rather than forwarding to a "delete done" page as we did with the
 earlier create example, it simply sets the C<status_msg> to display a
@@ -785,10 +788,10 @@ the "Delete" link next to the first "TCPIP_Illustrated_Vol-2".  A green
 along with a list of the eight remaining books.  You will also see the
 cascading delete operation via the DBIC_TRACE output:
 
-    SELECT me.id, me.title, me.rating FROM books me WHERE ( ( me.id = ? ) ): '6'
-    DELETE FROM books WHERE ( id = ? ): '6'
-    SELECT me.book_id, me.author_id FROM book_authors me WHERE ( me.book_id = ? ): '6'
-    DELETE FROM book_authors WHERE ( author_id = ? AND book_id = ? ): '4', '6'
+    SELECT me.id, me.title, me.rating FROM book me WHERE ( ( me.id = ? ) ): '6'
+    DELETE FROM book WHERE ( id = ? ): '6'
+    SELECT me.book_id, me.author_id FROM book_author me WHERE ( me.book_id = ? ): '6'
+    DELETE FROM book_author WHERE ( author_id = ? AND book_id = ? ): '4', '6'
 
 
 =head2 Fixing a Dangerous URL
@@ -825,7 +828,7 @@ C<sub delete> method to match:
         my ($self, $c) = @_;
 
         # Use the book object saved by 'object' and delete it along
-        # with related 'book_authors' entries
+        # with related 'book_author' entries
         $c->stash->{object}->delete;
 
         # Set a status message to be displayed at the top of the view
@@ -870,7 +873,7 @@ method to match the following:
         my ($self, $c) = @_;
 
         # Use the book object saved by 'object' and delete it along
-        # with related 'book_authors' entries
+        # with related 'book_author' entries
         $c->stash->{object}->delete;
 
         # Redirect the user back to the list page with status msg as an arg
@@ -936,10 +939,10 @@ Let's add two columns to our existing C<books> table to track when
 each book was added and when each book is updated:
 
     $ sqlite3 myapp.db
-    sqlite> ALTER TABLE books ADD created INTEGER;
-    sqlite> ALTER TABLE books ADD updated INTEGER;
-    sqlite> UPDATE books SET created = DATETIME('NOW'), updated = DATETIME('NOW');
-    sqlite> SELECT * FROM books;
+    sqlite> ALTER TABLE book ADD created INTEGER;
+    sqlite> ALTER TABLE book ADD updated INTEGER;
+    sqlite> UPDATE book SET created = DATETIME('NOW'), updated = DATETIME('NOW');
+    sqlite> SELECT * FROM book;
     1|CCSP SNRS Exam Certification Guide|5|2009-03-08 16:26:35|2009-03-08 16:26:35
     2|TCP/IP Illustrated, Volume 1|5|2009-03-08 16:26:35|2009-03-08 16:26:35
     3|Internetworking with TCP/IP Vol.1|4|2009-03-08 16:26:35|2009-03-08 16:26:35
@@ -970,7 +973,7 @@ Notice that we modified our use of the helper slightly: we told
 it to include the L<DBIx::Class::TimeStamp|DBIx::Class::TimeStamp>
 in the C<load_components> line of the Result Classes.
 
-If you open C<lib/MyApp/Schema/Result/Books.pm> in your editor you
+If you open C<lib/MyApp/Schema/Result/Book.pm> in your editor you
 should see that the C<created> and C<updated> fields are now included
 in the call to C<add_columns()>, but our relationship information below
 the "C<# DO NOT MODIFY...>" line was automatically preserved.
@@ -1011,7 +1014,7 @@ if you now use the sqlite3 command-line tool to dump the C<books> table,
 you will see that the new book we added has an appropriate date and
 time entered for it (see the last line in the listing below):
 
-    sqlite3 myapp.db "select * from books"
+    sqlite3 myapp.db "select * from book"
     1|CCSP SNRS Exam Certification Guide|5|2009-03-08 16:26:35|2009-03-08 16:26:35
     2|TCP/IP Illustrated, Volume 1|5|2009-03-08 16:26:35|2009-03-08 16:26:35
     3|Internetworking with TCP/IP Vol.1|4|2009-03-08 16:26:35|2009-03-08 16:26:35
@@ -1023,9 +1026,9 @@ time entered for it (see the last line in the listing below):
 Notice in the debug log that the SQL DBIC generated has changed to
 incorporate the datetime logic:
 
-    INSERT INTO books (created, rating, title, updated) VALUES (?, ?, ?, ?):
+    INSERT INTO book (created, rating, title, updated) VALUES (?, ?, ?, ?):
     '2009-03-08 16:29:08', '5', 'TCPIP_Illustrated_Vol-2', '2009-03-08 16:29:08'
-    INSERT INTO book_authors (author_id, book_id) VALUES (?, ?): '4', '10'
+    INSERT INTO book_author (author_id, book_id) VALUES (?, ?): '4', '10'
 
 
 =head2 Create a ResultSet Class
@@ -1044,9 +1047,9 @@ making a directory where DBIx::Class will look for our ResultSet Class:
 
     mkdir lib/MyApp/Schema/ResultSet
 
-Then open C<lib/MyApp/Schema/ResultSet/Books.pm> and enter the following:
+Then open C<lib/MyApp/Schema/ResultSet/Book.pm> and enter the following:
 
-    package MyApp::Schema::ResultSet::Books;
+    package MyApp::Schema::ResultSet::Book;
 
     use strict;
     use warnings;
@@ -1072,13 +1075,13 @@ Then open C<lib/MyApp/Schema/ResultSet/Books.pm> and enter the following:
     1;
 
 Then we need to tell the Result Class to to treat this as a ResultSet
-Class.  Open C<lib/MyApp/Schema/Result/Books.pm> and add the following
+Class.  Open C<lib/MyApp/Schema/Result/Book.pm> and add the following
 above the "C<1;>" at the bottom of the file:
 
     #
     # Set ResultSet Class
     #
-    __PACKAGE__->resultset_class('MyApp::Schema::ResultSet::Books');
+    __PACKAGE__->resultset_class('MyApp::Schema::ResultSet::Book');
 
 Then add the following method to the C<lib/MyApp/Controller/Books.pm>:
 
@@ -1094,7 +1097,7 @@ Then add the following method to the C<lib/MyApp/Controller/Books.pm>:
         # Retrieve all of the book records as book model objects and store in the
         # stash where they can be accessed by the TT template, but only
         # retrieve books created within the last $min number of minutes
-        $c->stash->{books} = [$c->model('DB::Books')
+        $c->stash->{books} = [$c->model('DB::Book')
                                 ->created_after(DateTime->now->subtract(minutes => $mins))];
 
         # Set the TT template to use.  You will almost always want to do this
@@ -1144,7 +1147,7 @@ C<lib/MyApp/Controller/Books.pm> and add the following method:
         # stash where they can be accessed by the TT template, but only
         # retrieve books created within the last $min number of minutes
         # AND that have 'TCP' in the title
-        $c->stash->{books} = [$c->model('DB::Books')
+        $c->stash->{books} = [$c->model('DB::Book')
                                 ->created_after(DateTime->now->subtract(minutes => $mins))
                                 ->search({title => {'like', '%TCP%'}})
                              ];
@@ -1174,13 +1177,13 @@ how recently you added books to your database):
 Take a look at the DBIC_TRACE output in the development server log for
 the first URL and you should see something similar to the following:
 
-    SELECT me.id, me.title, me.rating, me.created, me.updated FROM books me
+    SELECT me.id, me.title, me.rating, me.created, me.updated FROM book me
     WHERE ( ( ( title LIKE ? ) AND ( created > ? ) ) ): '%TCP%', '2009-03-08 14:52:54'
 
 However, let's not pollute our controller code with this raw "TCP"
 query -- it would be cleaner to encapsulate that code in a method on
 our ResultSet Class.  To do this, open
-C<lib/MyApp/Schema/ResultSet/Books.pm> and add the following method:
+C<lib/MyApp/Schema/ResultSet/Book.pm> and add the following method:
 
     =head2 title_like
 
@@ -1215,7 +1218,7 @@ shown here -- the rest of the method should be the same):
         # stash where they can be accessed by the TT template, but only
         # retrieve books created within the last $min number of minutes
         # AND that have 'TCP' in the title
-        $c->stash->{books} = [$c->model('DB::Books')
+        $c->stash->{books} = [$c->model('DB::Book')
                                 ->created_after(DateTime->now->subtract(minutes => $mins))
                                 ->title_like('TCP')
                              ];
@@ -1242,7 +1245,7 @@ well.  Whereas the ResultSet construct is used in DBIC to correspond
 to an entire query, the Result Class construct is used to represent a 
 row. Therefore, we can add row-specific "helper methods" to our Result 
 Classes stored in C<lib/MyApp/Schema/Result/>. For example, open 
-C<lib/MyApp/Schema/Result/Authors.pm> and add the following method (as 
+C<lib/MyApp/Schema/Result/Author.pm> and add the following method (as 
 always, it must be above the closing "C<1;>"):
 
     #
@@ -1260,14 +1263,14 @@ and change the definition of C<tt_authors> from this:
 
     ...
       [% tt_authors = [ ];
-         tt_authors.push(author.last_name) FOREACH author = book.authors %]
+         tt_authors.push(author.last_name) FOREACH author = book.author %]
     ...
 
 to:
 
     ...
       [% tt_authors = [ ];
-         tt_authors.push(author.full_name) FOREACH author = book.authors %]
+         tt_authors.push(author.full_name) FOREACH author = book.author %]
     ...
 
 (Only C<author.last_name> was changed to C<author.full_name> -- the
index 978fb91..764566a 100644 (file)
@@ -219,7 +219,7 @@ previous step):
     | /                                   | /index                               |
     '-------------------------------------+--------------------------------------'
     
-    [info] Hello powered by Catalyst 5.71000
+    [info] Hello powered by Catalyst 5.80003
     You can connect to your server at http://debian:3000
 
 Point your web browser to L<http://localhost:3000> (substituting a 
index 22d7286..fe78ff2 100644 (file)
@@ -128,7 +128,7 @@ you can obviously indent them if you prefer):
             
         # Retrieve all of the book records as book model objects and store in the
         # stash where they can be accessed by the TT template
-        $c->stash->{books} = [$c->model('DB::Books')->all];
+        $c->stash->{books} = [$c->model('DB::Book')->all];
             
         # Set the TT template to use.  You will almost always want to do this
         # in your action methods.
@@ -140,6 +140,10 @@ encountered (it has no effect when Perl is run without the C<-d> flag).
 
 B<NOTE:> The C<DB> here is the Perl Debugger, not the DB model.
 
+If you haven't done it already, enable SQL logging as before:
+
+    $ export DBIC_TRACE=1
+
 To now run the Catalyst development server under the Perl debugger, simply 
 prepend C<perl -d> to the front of C<script/myapp_server.pl>:
 
@@ -169,7 +173,7 @@ C<MyApp::Controller::list> method, the console session running the
 development server will drop to the Perl debugger prompt:
 
     MyApp::Controller::Books::list(/home/me/MyApp/script/../lib/MyApp/Controller/Books.pm:48):
-    48:         $c->stash->{books} = [$c->model('DB::Books')->all];
+    48:         $c->stash->{books} = [$c->model('DB::Book')->all];
     
       DB<1>
 
@@ -179,7 +183,7 @@ model (C<n> jumps over method/subroutine calls; you can also use C<s> to
 C<single-step> into methods/subroutines):
 
       DB<1> n
-    SELECT me.id, me.authors, me.title, me.rating FROM books me:
+    SELECT me.id, me.title, me.rating, me.created, me.updated FROM book me:
     MyApp::Controller::Books::list(/home/me/MyApp/script/../lib/MyApp/Controller/Books.pm:53):
     53:         $c->stash->{template} = 'books/list.tt2';
     
@@ -191,12 +195,14 @@ output also shows up in the development server debug information.
 
 Next, list the methods available on our C<Book> model:
 
-      DB<1> m $c->model('DB::Books')
+      DB<1> m $c->model('DB::Book')
     ()
     (0+
     (bool
+    __result_class_accessor
     __source_handle_accessor
     _add_alias
+    __bool
     _build_unique_query
     _calculate_score
     _collapse_cond
@@ -206,8 +212,8 @@ Next, list the methods available on our C<Book> model:
 
 We can also play with the model directly:
 
-      DB<2> x ($c->model('DB::Books')->all)[1]->title
-    SELECT me.id, me.title, me.rating FROM books me:
+      DB<2> x ($c->model('DB::Book')->all)[1]->title
+    SELECT me.id, me.title, me.rating, me.created, me.updated FROM book me:
     0  'TCP/IP Illustrated, Volume 1'
 
 This uses the Perl debugger C<x> command to display the title of a book.
@@ -219,9 +225,11 @@ argument to the C<x> command limits the depth of the dump to 4 levels):
     0  ARRAY(0xa8f3b7c)
        0  MyApp::Model::DB::Book=HASH(0xb8e702c)
           '_column_data' => HASH(0xb8e5e2c)
+             'created' => '2009-05-08 10:19:46'
              'id' => 1
              'rating' => 5
              'title' => 'CCSP SNRS Exam Certification Guide'
+             'updated' => '2009-05-08 10:19:46'
           '_in_storage' => 1
     <lines removed for brevity>
 
@@ -233,16 +241,32 @@ breakpoint is hit (or the application exits):
 
 Finally, press C<Ctrl+C> to break out of the development server.
 Because we are running inside the Perl debugger, you will drop to the
-debugger prompt.  Press C<q> to exit the debugger and return to your OS
+debugger prompt.  
+
+    ^CCatalyst::Engine::HTTP::run(/usr/local/share/perl/5.10.0/Catalyst/Engine/HTTP.pm:260):
+    260:            while ( accept( Remote, $daemon ) ) {
+
+    DB<4>
+
+Finally, press C<q> to exit the debugger and return to your OS
 shell prompt:
 
       DB<4> q
     $
 
 For more information on using the Perl debugger, please see C<perldebug>
-and C<perldebtut>.  You can also type C<h> or C<h h> at the debugger
-prompt to view the built-in help screens.
+and C<perldebtut>.  For those daring souls out there, you can dive down
+even deeper into the magical depths of this fine debugger by checking
+out C<perldebguts>.
 
+You can also type C<h> or C<h h> at the debugger prompt to view the
+built-in help screens.
+
+For an excellent book covering all aspects of the Perl debugger, we highly
+recommend reading 'Pro Perl Debugging' by Richard Foley.
+
+Oh yeah, before you forget, be sure to remove the C<DB::single=1> line you
+added above in C<lib/MyApp/Controller/Books.pm>.
 
 =head1 DEBUGGING MODULES FROM CPAN
 
@@ -264,9 +288,9 @@ copy of an installed module:
 
     mkdir -p lib/Module; cp `perldoc -l Module::Name` lib/Module/
 
-Note: If you are following along in Debian 5, you will need to install
-the C<perl-doc> package to use the C<perldoc> command.  Use 
-C<sudo aptitude install perl-doc> to do that.
+Note: If you are following along in Debian 5 or Ubuntu, you will
+need to install the C<perl-doc> package to use the C<perldoc> command.  
+Use C<sudo aptitude install perl-doc> to do that.
 
 For example, you could make a copy of 
 L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>
@@ -295,6 +319,12 @@ For example:
         'print $Catalyst::Plugin::Authentication::VERSION;'
     0.07
 
+and if you are using bash aliases:
+
+    alias pmver="perl -le '\$m = shift; eval qq(require \$m) \
+        or die qq(module \"\$m\" is not installed\\n); \
+        print \$m->VERSION'"
+
 =item * 
 
 Check if a modules contains a given method:
@@ -336,6 +366,7 @@ with the tutorial> (especially the 'undef' option -- leaving this
 enabled will conflict with several of the conventions used by this 
 tutorial to leave some variables undefined on purpose).
 
+Happy debugging.
 
 =head1 AUTHOR
 
index 41f58bd..7e54dcb 100644 (file)
@@ -190,11 +190,11 @@ Then replace it with:
 
     # Load plugins
     use Catalyst qw/-Debug
-                ConfigLoader
-                Static::Simple
+                    ConfigLoader
+                    Static::Simple
                 
-                StackTrace
-                /;
+                    StackTrace
+                    /;
 
 B<Note:> Recent versions of C<Catalyst::Devel> have used a variety of 
 techniques to load these plugins/flags.  For example, you might see
@@ -213,6 +213,14 @@ L<StackTrace|Catalyst::Plugin::StackTrace> output appears in your
 browser, not in the console window from which you're running your 
 application, which is where logging output usually goes.
 
+Make sure that when adding new plugins that you include them as a new
+dependancies within the Makefile.PL file. For example, after adding
+the StackTrace plugin the Makefile.PL should include the following
+line:
+
+    requires 'Catalyst::Plugin::StackTrace';
+
+
 B<Notes:> 
 
 =over 4
@@ -273,7 +281,7 @@ and add the following method to the controller:
     
         # Retrieve all of the book records as book model objects and store in the
         # stash where they can be accessed by the TT template
-        # $c->stash->{books} = [$c->model('DB::Books')->all];
+        # $c->stash->{books} = [$c->model('DB::Book')->all];
         # But, for now, use this code until we create the model later
         $c->stash->{books} = '';
     
@@ -559,18 +567,18 @@ C<myapp01.sql> in your editor and enter:
     --
     -- Create a very simple database to hold book and author information
     --
-    CREATE TABLE books (
+    CREATE TABLE book (
             id          INTEGER PRIMARY KEY,
             title       TEXT ,
             rating      INTEGER
     );
-    -- 'book_authors' is a many-to-many join table between books & authors
-    CREATE TABLE book_authors (
+    -- 'book_author' is a many-to-many join table between books & authors
+    CREATE TABLE book_author (
             book_id     INTEGER,
             author_id   INTEGER,
             PRIMARY KEY (book_id, author_id)
     );
-    CREATE TABLE authors (
+    CREATE TABLE author (
             id          INTEGER PRIMARY KEY,
             first_name  TEXT,
             last_name   TEXT
@@ -578,27 +586,27 @@ C<myapp01.sql> in your editor and enter:
     ---
     --- Load some sample data
     ---
-    INSERT INTO books VALUES (1, 'CCSP SNRS Exam Certification Guide', 5);
-    INSERT INTO books VALUES (2, 'TCP/IP Illustrated, Volume 1', 5);
-    INSERT INTO books VALUES (3, 'Internetworking with TCP/IP Vol.1', 4);
-    INSERT INTO books VALUES (4, 'Perl Cookbook', 5);
-    INSERT INTO books VALUES (5, 'Designing with Web Standards', 5);
-    INSERT INTO authors VALUES (1, 'Greg', 'Bastien');
-    INSERT INTO authors VALUES (2, 'Sara', 'Nasseh');
-    INSERT INTO authors VALUES (3, 'Christian', 'Degu');
-    INSERT INTO authors VALUES (4, 'Richard', 'Stevens');
-    INSERT INTO authors VALUES (5, 'Douglas', 'Comer');
-    INSERT INTO authors VALUES (6, 'Tom', 'Christiansen');
-    INSERT INTO authors VALUES (7, 'Nathan', 'Torkington');
-    INSERT INTO authors VALUES (8, 'Jeffrey', 'Zeldman');
-    INSERT INTO book_authors VALUES (1, 1);
-    INSERT INTO book_authors VALUES (1, 2);
-    INSERT INTO book_authors VALUES (1, 3);
-    INSERT INTO book_authors VALUES (2, 4);
-    INSERT INTO book_authors VALUES (3, 5);
-    INSERT INTO book_authors VALUES (4, 6);
-    INSERT INTO book_authors VALUES (4, 7);
-    INSERT INTO book_authors VALUES (5, 8);
+    INSERT INTO book VALUES (1, 'CCSP SNRS Exam Certification Guide', 5);
+    INSERT INTO book VALUES (2, 'TCP/IP Illustrated, Volume 1', 5);
+    INSERT INTO book VALUES (3, 'Internetworking with TCP/IP Vol.1', 4);
+    INSERT INTO book VALUES (4, 'Perl Cookbook', 5);
+    INSERT INTO book VALUES (5, 'Designing with Web Standards', 5);
+    INSERT INTO author VALUES (1, 'Greg', 'Bastien');
+    INSERT INTO author VALUES (2, 'Sara', 'Nasseh');
+    INSERT INTO author VALUES (3, 'Christian', 'Degu');
+    INSERT INTO author VALUES (4, 'Richard', 'Stevens');
+    INSERT INTO author VALUES (5, 'Douglas', 'Comer');
+    INSERT INTO author VALUES (6, 'Tom', 'Christiansen');
+    INSERT INTO author VALUES (7, 'Nathan', 'Torkington');
+    INSERT INTO author VALUES (8, 'Jeffrey', 'Zeldman');
+    INSERT INTO book_author VALUES (1, 1);
+    INSERT INTO book_author VALUES (1, 2);
+    INSERT INTO book_author VALUES (1, 3);
+    INSERT INTO book_author VALUES (2, 4);
+    INSERT INTO book_author VALUES (3, 5);
+    INSERT INTO book_author VALUES (4, 6);
+    INSERT INTO book_author VALUES (4, 7);
+    INSERT INTO book_author VALUES (5, 8);
 
 Then use the following command to build a C<myapp.db> SQLite database:
 
@@ -615,7 +623,7 @@ database contents:
     $ sqlite3 myapp.db
     SQLite version 3.5.9
     Enter ".help" for instructions
-    sqlite> select * from books;
+    sqlite> select * from book;
     1|CCSP SNRS Exam Certification Guide|5
     2|TCP/IP Illustrated, Volume 1|5
     3|Internetworking with TCP/IP Vol.1|4
@@ -626,7 +634,7 @@ database contents:
 
 Or:
 
-    $ sqlite3 myapp.db "select * from books"
+    $ sqlite3 myapp.db "select * from book"
     1|CCSP SNRS Exam Certification Guide|5
     2|TCP/IP Illustrated, Volume 1|5
     3|Internetworking with TCP/IP Vol.1|4
@@ -639,6 +647,15 @@ 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.
 
+Please note that here we have chosen to use 'singular' table names. This
+is because the default inflection code for L<DBIx::Class:Schema::Loader>
+does NOT handle plurals. There has been much philosophical discussion
+on whether table names should be plural or singular. There is no one
+correct answer, as long as one makes a choice and remains consistent
+with it. If you prefer plural table names (e.g. they are easier and
+more natural to read) then you will need to pass it an inflect_map 
+option. See L<DBIx::Class:Schema::Loader> for more information.
+
 For using other databases, such as PostgreSQL or MySQL, see 
 L<Appendix 2|Catalyst::Manual::Tutorial::Appendices>.
 
@@ -675,6 +692,9 @@ running this command:
         'print "$Catalyst::Model::DBIC::Schema::VERSION\n"'
     0.23
 
+(please note that the '\' above is a line continuation marker and
+should NOT be included as part of the command)
+
 If you don't have version 0.23 or higher, please run this command
 to install it directly from CPAN:
 
@@ -701,6 +721,9 @@ automatically build the required files for us:
     created "/home/me/MyApp/script/../lib/MyApp/Model/DB.pm"
     created "/home/me/MyApp/script/../t/model_DB.t"
 
+(please note that the '\' above is a line continuation marker and
+should NOT be included as part of the command)
+
 The C<script/myapp_create.pl> command breaks down like this:
 
 =over 4
@@ -743,7 +766,7 @@ only contains a call to the C<load_namespaces> method.  You will also
 find that C<lib/MyApp> contains a C<Schema> subdirectory, which then 
 has a subdirectory called "Result".  This "Result" subdirectory then 
 has files named according to each of the tables in our simple database 
-(C<Authors.pm>, C<BookAuthors.pm>, and C<Books.pm>).  These three 
+(C<Author.pm>, C<BookAuthor.pm>, and C<Book.pm>).  These three 
 files are called "Result Classes" in DBIx::Class nomenclature. Although the 
 Result Class files are named after tables in our database, the classes 
 correspond to the I<row-level data> that is returned by DBIC (more on 
@@ -785,6 +808,10 @@ have v0.23 C<Catalyst::Model::DBIC::Schema> as discussed above):
     $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
         create=static components=TimeStamp dbi:SQLite:myapp.db
     $
+    $ # Note that the '\' above is a line continuation marker and
+    $ # should NOT be included as part of the command
+
+    $
     $ # Now convert the existing files over
     $ cd lib/MyApp/Schema
     $ perl -MIO::All -e 'for (@ARGV) { my $s < io($_); $s =~ s/.*\n\# You can replace.*?\n//s;
@@ -806,7 +833,7 @@ C<lib/MyApp/Schema/Result> (we will be starting to add some
 
 Open C<lib/MyApp/Controller/Books.pm> and un-comment the model code we 
 left disabled earlier so that your version matches the following (un-
-comment the line containing C<[$c-E<gt>model('DB::Books')-E<gt>all]> 
+comment the line containing C<[$c-E<gt>model('DB::Book')-E<gt>all]> 
 and delete the next 2 lines):
 
     =head2 list
@@ -823,7 +850,7 @@ and delete the next 2 lines):
     
         # Retrieve all of the book records as book model objects and store in the
         # stash where they can be accessed by the TT template
-        $c->stash->{books} = [$c->model('DB::Books')->all];
+        $c->stash->{books} = [$c->model('DB::Book')->all];
     
         # Set the TT template to use.  You will almost always want to do this
         # in your action methods (action methods respond to user input in
@@ -831,8 +858,8 @@ and delete the next 2 lines):
         $c->stash->{template} = 'books/list.tt2';
     }
 
-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 
+B<TIP>: You may see the C<$c-E<gt>model('DB::Book')> un-commented 
+above written as C<$c-E<gt>model('DB')-E<gt>resultset('Book')>.  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 is 
@@ -843,7 +870,7 @@ 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'});
+    $c->model('DB::Book')->search({}, {order_by => 'title DESC'});
 
 Some other examples are provided in 
 L<DBIx::Class::Manual::Cookbook/Complex WHERE clauses>, with 
@@ -880,9 +907,9 @@ display something like:
     [debug] Statistics enabled
     [debug] Loaded plugins:
     .----------------------------------------------------------------------------.
-    | Catalyst::Plugin::ConfigLoader  0.20                                       |
-    | Catalyst::Plugin::StackTrace  0.08                                         |
-    | Catalyst::Plugin::Static::Simple  0.20                                     |
+    | Catalyst::Plugin::ConfigLoader  0.23                                       |
+    | Catalyst::Plugin::StackTrace  0.10                                         |
+    | Catalyst::Plugin::Static::Simple  0.21                                     |
     '----------------------------------------------------------------------------'
     
     [debug] Loaded dispatcher "Catalyst::Dispatcher"
@@ -896,9 +923,9 @@ display something like:
     | MyApp::Controller::Books                                        | instance |
     | MyApp::Controller::Root                                         | instance |
     | MyApp::Model::DB                                                | instance |
-    | MyApp::Model::DB::Authors                                       | class    |
-    | MyApp::Model::DB::BookAuthors                                   | class    |
-    | MyApp::Model::DB::Books                                         | class    |
+    | MyApp::Model::DB::Author                                        | class    |
+    | MyApp::Model::DB::Book                                          | class    |
+    | MyApp::Model::DB::BookAuthor                                    | class    |
     | MyApp::View::TT                                                 | instance |
     '-----------------------------------------------------------------+----------'
     
@@ -923,7 +950,7 @@ display something like:
     | /books/list                         | /books/list                          |
     '-------------------------------------+--------------------------------------'
     
-    [info] MyApp powered by Catalyst 5.71000
+    [info] MyApp powered by Catalyst 5.80003
     You can connect to your server at http://debian:3000
 
 B<NOTE:> Be sure you run the C<script/myapp_server.pl> command from
@@ -941,8 +968,8 @@ Some things you should note in the output above:
 
 Catalyst::Model::DBIC::Schema dynamically created three model classes,
 one to represent each of the three tables in our database
-(C<MyApp::Model::DB::Authors>, C<MyApp::Model::DB::BookAuthors>,
-and C<MyApp::Model::DB::Books>).
+(C<MyApp::Model::DB::Author>, C<MyApp::Model::DB::BookAuthor>,
+and C<MyApp::Model::DB::Book>).
 
 =item *
 
@@ -1151,7 +1178,7 @@ Let's manually add some relationship information to the auto-generated
 Result Class files. (Note: if you are using a database other than 
 SQLite, such as PostgreSQL, then the relationship could have been 
 automatically placed in the Result Class files.  If so, you can skip 
-this step.)  First edit C<lib/MyApp/Schema/Result/Books.pm> and add the 
+this step.)  First edit C<lib/MyApp/Schema/Result/Book.pm> and add the 
 following text below the C<# You can replace this text...> comment:
 
     #
@@ -1163,7 +1190,7 @@ following text below the C<# You can replace this text...> comment:
     #     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 (aka, foreign key in peer table)
-    __PACKAGE__->has_many(book_authors => 'MyApp::Schema::Result::BookAuthors', 'book_id');
+    __PACKAGE__->has_many(book_author => 'MyApp::Schema::Result::BookAuthor', 'book_id');
     
     # many_to_many():
     #   args:
@@ -1171,7 +1198,7 @@ following text below the C<# You can replace this text...> comment:
     #     2) Name of has_many() relationship this many_to_many() is shortcut for
     #     3) Name of belongs_to() relationship in model class of has_many() above
     #   You must already have the has_many() defined to use a many_to_many().
-    __PACKAGE__->many_to_many(authors => 'book_authors', 'author');
+    __PACKAGE__->many_to_many(author => 'book_author', 'author');
 
 
 B<Note:> Be careful to put this code I<above> the C<1;> at the end of the
@@ -1179,31 +1206,20 @@ file.  As with any Perl package, we need to end the last line with
 a statement that evaluates to C<true>.  This is customarily done with
 C<1;> on a line by itself.
 
-C<Important Note:> Although this tutorial uses plural names for both 
-the names of the SQL tables and therefore the Result Classes (after 
-all, C<Schema::Loader> automatically named the Result Classes from the 
-names of the SQL tables it found), DBIx::Class users prefer singular 
-names for these items.  B<Please try to use singular table and DBIC 
-model/Result Class names in your applications.>  This tutorial will 
-migrate to singular names as soon as possible (patches welcomed). 
-B<Note that while singular is preferred for the DBIC model, plural is 
-perfectly acceptable for the names of the controller classes.>  After 
-all, the C<Books.pm> controller operates on multiple books.
-
 This code defines both a C<has_many> and a C<many_to_many> 
 relationship. The C<many_to_many> relationship is optional, but it 
 makes it easier to map a book to its collection of authors.  Without 
-it, we would have to "walk" though the C<book_authors> table as in 
-C<$book-E<gt>book_authors-E<gt>first-E<gt>author-E<gt>last_name> (we 
+it, we would have to "walk" though the C<book_author> table as in 
+C<$book-E<gt>book_author-E<gt>first-E<gt>author-E<gt>last_name> (we 
 will see examples on how to use DBIx::Class objects in your code soon, 
-but note that because C<$book-E<gt>book_authors> can return multiple 
+but note that because C<$book-E<gt>book_author> can return multiple 
 authors, we have to use C<first> to display a single author). 
-C<many_to_many> allows us to use the shorter C<$book-E<gt>authors-
+C<many_to_many> allows us to use the shorter C<$book-E<gt>author-
 E<gt>first-E<gt>last_name>. Note that you cannot define a 
 C<many_to_many> relationship without also having the C<has_many> 
 relationship in place.
 
-Then edit C<lib/MyApp/Schema/Result/Authors.pm> and add relationship
+Then edit C<lib/MyApp/Schema/Result/Author.pm> and add relationship
 information as follows (again, be careful to put in above the C<1;> but
 below the C<# DO NOT MODIFY THIS OR ANYTHING ABOVE!> comment):
 
@@ -1216,7 +1232,7 @@ below the C<# DO NOT MODIFY THIS OR ANYTHING ABOVE!> comment):
     #     1) Name of relationship, DBIC will create an accessor with this name
     #     2) Name of the model class referenced by this relationship
     #     3) Column name in *foreign* table (aka, foreign key in peer table)
-    __PACKAGE__->has_many(book_author => 'MyApp::Schema::Result::BookAuthors', 'author_id');
+    __PACKAGE__->has_many(book_author => 'MyApp::Schema::Result::BookAuthor', 'author_id');
     
     # many_to_many():
     #   args:
@@ -1224,10 +1240,10 @@ below the C<# DO NOT MODIFY THIS OR ANYTHING ABOVE!> comment):
     #     2) Name of has_many() relationship this many_to_many() is shortcut for
     #     3) Name of belongs_to() relationship in model class of has_many() above
     #   You must already have the has_many() defined to use a many_to_many().
-    __PACKAGE__->many_to_many(books => 'book_author', 'book');
+    __PACKAGE__->many_to_many(book => 'book_author', 'book');
 
 Finally, do the same for the "join table,"
-C<lib/MyApp/Schema/Result/BookAuthors.pm>:
+C<lib/MyApp/Schema/Result/BookAuthor.pm>:
 
     #
     # Set relationships:
@@ -1238,14 +1254,14 @@ C<lib/MyApp/Schema/Result/BookAuthors.pm>:
     #     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 *this* table
-    __PACKAGE__->belongs_to(book => 'MyApp::Schema::Result::Books', 'book_id');
+    __PACKAGE__->belongs_to(book => 'MyApp::Schema::Result::Book', 'book_id');
     
     # belongs_to():
     #   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 *this* table
-    __PACKAGE__->belongs_to(author => 'MyApp::Schema::Result::Authors', 'author_id');
+    __PACKAGE__->belongs_to(author => 'MyApp::Schema::Result::Author', 'author_id');
 
 
 =head2 Run The Application
@@ -1275,7 +1291,7 @@ template to do that.
 Let's add a new column to our book list page that takes advantage of 
 the relationship information we manually added to our schema files in 
 the previous section.  Edit C<root/src/books/list.tt2> and replace
-the "empty" tabase cell with the following:
+the "empty" table cell "<td></td>" with the following:
 
     ...
     <td>
@@ -1310,16 +1326,16 @@ debug output (one for each book as the authors are being retrieved by
 DBIx::Class):
 
     SELECT me.id, me.title, me.rating FROM books me:
-    SELECT author.id, author.first_name, author.last_name FROM book_authors me  
-    JOIN authors author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '1'
-    SELECT author.id, author.first_name, author.last_name FROM book_authors me  
-    JOIN authors author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '2'
-    SELECT author.id, author.first_name, author.last_name FROM book_authors me  
-    JOIN authors author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '3'
-    SELECT author.id, author.first_name, author.last_name FROM book_authors me  
-    JOIN authors author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '4'
-    SELECT author.id, author.first_name, author.last_name FROM book_authors me  
-    JOIN authors author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '5'
+    SELECT author.id, author.first_name, author.last_name FROM book_author me  
+    JOIN author author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '1'
+    SELECT author.id, author.first_name, author.last_name FROM book_author me  
+    JOIN author author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '2'
+    SELECT author.id, author.first_name, author.last_name FROM book_author me  
+    JOIN author author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '3'
+    SELECT author.id, author.first_name, author.last_name FROM book_author me  
+    JOIN author author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '4'
+    SELECT author.id, author.first_name, author.last_name FROM book_author me  
+    JOIN author author ON ( author.id = me.author_id ) 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; 
@@ -1433,7 +1449,7 @@ has changed):
     
         # Retrieve all of the book records as book model objects and store in the
         # stash where they can be accessed by the TT template
-        $c->stash->{books} = [$c->model('DB::Books')->all];
+        $c->stash->{books} = [$c->model('DB::Book')->all];
     
         # Set the TT template to use.  You will almost always want to do this
         # in your action methods (actions methods respond to user input in
@@ -1451,6 +1467,8 @@ you will B<not> be able to use either the C<$c-E<gt>forward> or
 the C<$c-E<gt>detach> mechanisms (these are discussed in Chapter 2 and
 Chapter 9 of the Tutorial).
 
+B<IMPORTANT:> Make sure that you do NOT skip the following section
+before continuing to the next chapter 4 Basic CRUD.
 
 =head2 Return To A Manually Specified Template
 
index eb67ebb..04c08ab 100644 (file)
@@ -59,7 +59,7 @@ L<Appendices|Catalyst::Manual::Tutorial::Appendices>
 You may have noticed that the Catalyst Helper scripts automatically 
 create basic C<.t> test scripts under the C<t> directory.  This 
 chapter of the tutorial briefly looks at how these tests can be used 
-to not only ensure that your application is working correctly at the 
+not only to ensure that your application is working correctly at the 
 present time, but also provide automated regression testing as you 
 upgrade various pieces of your application over time.
 
@@ -67,6 +67,10 @@ You can check out the source code for this example from the Catalyst
 Subversion repository as per the instructions in
 L<Catalyst::Manual::Tutorial::Intro|Catalyst::Manual::Tutorial::Intro>.
 
+For an excellent introduction to learning the many benefits of testing
+your Perl applications and modules, you might want to read 'Perl Testing: 
+A Developer's Notebook' by Ian Langworth and chromatic.
+
 
 =head1 RUNNING THE "CANNED" CATALYST TESTS
 
@@ -83,7 +87,7 @@ a quick and easy way to reduce the clutter).  Look for lines like this
 for errors:
 
     #   Failed test 'Request should succeed'
-    #   in t/controller_Books.t at line 8.
+    #   at t/controller_Books.t line 8.
     # Looks like you failed 1 test of 3.
 
 The redirection used by the Authentication plugins will cause several 
@@ -98,13 +102,25 @@ to:
 
     ok( request('/login')->is_success, 'Request should succeed' );
 
-2) Change the "C<request('/logout')-E<gt>is_success>" to 
-"C<request('/logout')-E<gt>is_redirect>" in C<t/controller_Logout.t>.
+2) Change the line in C<t/controller_Logout.t> that reads:
+
+    ok( request('/logout')->is_success, 'Request should succeed' );
+
+to:
+
+    ok( request('/logout')->is_redirect, 'Request should succeed' );
+
+3) Change the line in C<t/controller_Books.t> that reads:
+
+    ok( request('/books')->is_success, 'Request should succeed' );
+
+to:
+
+    ok( request('/books')->is_redirect, 'Request should succeed' );
 
-3) Change the "C<request('/books')-E<gt>is_success>" to 
-"C<request('/books')-E<gt>is_redirect>" in C<t/controller_Books.t>.
+4) Add the following statement to the top of C<t/view_TT.t>:
 
-4) Add "C<use MyApp;>" to the top of C<t/view_TT.t>.
+    use MyApp;
 
 As you can see in the C<prove> command line above, the C<--lib> option
 is used to set the location of the Catalyst C<lib> directory.  With this
@@ -118,7 +134,7 @@ environment variable.  For example:
 
 B<Note:> Depending on the versions of various modules you have 
 installed, you might get some C<used only once> warnings -- you can 
-ignore these.  If you want to elliminate the warnings, you can 
+ignore these.  If you want to eliminate the warnings, you can 
 edit C<Template::Base> to disable and then re-enable warnings
 are the C</usr/lib/perl5/Template/Base.pm> line in C<sub new>.
 You can locate where C<Template::Base> is located with the 
@@ -221,12 +237,7 @@ editor and enter the following:
     # Log in as each user
     # Specify username and password on the URL
     $ua1->get_ok("http://localhost/login?username=test01&password=mypass", "Login 'test01'");
-    # Use the form for user 'test02'; note there is no description here
-    $ua2->submit_form(
-        fields => {
-            username => 'test02',
-            password => 'mypass',
-        });
+    $ua1->get_ok("http://localhost/login?username=test02&password=mypass", "Login 'test02'");
     
     # Go back to the login page and it should show that we are already logged in
     $_->get_ok("http://localhost/login", "Return to '/login'") for $ua1, $ua2;