Moving tutorial POD here
Jonathan Rockway [Fri, 27 Oct 2006 16:53:28 +0000 (16:53 +0000)]
lib/Catalyst/Manual.pm [new file with mode: 0644]
lib/Catalyst/Manual/Tutorial/Tutorial/AdvancedCRUD.pod [new file with mode: 0644]
lib/Catalyst/Manual/Tutorial/Tutorial/Appendices.pod [new file with mode: 0644]
lib/Catalyst/Manual/Tutorial/Tutorial/Authentication.pod [new file with mode: 0644]
lib/Catalyst/Manual/Tutorial/Tutorial/Authorization.pod [new file with mode: 0644]
lib/Catalyst/Manual/Tutorial/Tutorial/BasicCRUD.pod [new file with mode: 0644]
lib/Catalyst/Manual/Tutorial/Tutorial/CatalystBasics.pod [new file with mode: 0644]
lib/Catalyst/Manual/Tutorial/Tutorial/Debugging.pod [new file with mode: 0644]
lib/Catalyst/Manual/Tutorial/Tutorial/Intro.pod [new file with mode: 0644]
lib/Catalyst/Manual/Tutorial/Tutorial/Testing.pod [new file with mode: 0644]

diff --git a/lib/Catalyst/Manual.pm b/lib/Catalyst/Manual.pm
new file mode 100644 (file)
index 0000000..01fa2a4
--- /dev/null
@@ -0,0 +1,25 @@
+#!perl
+# Manual.pm 
+# Copyright (c) 2006 Jonathan Rockway <jrockway@cpan.org>
+
+package Catalyst::Manual;
+
+=head1 NAME
+
+Catalyst::Manual - pacakge holding the Catalyst developer's manual and tutorial
+
+=head1 SYNOPSIS
+
+    perldoc Catalyst::Manual::Intro
+    perldoc Catalyst::Tutorial
+
+=head1 SEE ALSO
+
+Get L<Task::Catalyst::Tutorial|Task::Catalyst::Tutorial> to install
+all the dependencies you need to follow along with the Tutorial.
+
+=cut
+
+our $VERSION = '0.01';
+
+1;
diff --git a/lib/Catalyst/Manual/Tutorial/Tutorial/AdvancedCRUD.pod b/lib/Catalyst/Manual/Tutorial/Tutorial/AdvancedCRUD.pod
new file mode 100644 (file)
index 0000000..77b81cd
--- /dev/null
@@ -0,0 +1,764 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::AdvancedCRUD - Catalyst Tutorial - Part 8: Advanced CRUD
+
+
+=head1 OVERVIEW
+
+This is B<Part 8 of 9> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
+
+=item 3
+
+L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>
+
+=item 4
+
+L<Authentication|Catalyst::Manual::Tutorial::Authentication>
+
+=item 5
+
+L<Authorization|Catalyst::Manual::Tutorial::Authorization>
+
+=item 6
+
+L<Debugging|Catalyst::Manual::Tutorial::Debugging>
+
+=item 7
+
+L<Testing|Catalyst::Manual::Tutorial::Testing>
+
+=item 8
+
+B<AdvancedCRUD>
+
+=item 9
+
+L<Appendices|Catalyst::Manual::Tutorial::Appendices>
+
+=back
+
+=head1 DESCRIPTION
+
+This part of the tutorial explores more advanced functionality for
+Create, Read, Update, and Delete (CRUD) than we saw in Part 3.  In
+particular, it looks at a number of techniques that can be useful for
+the Update portion of CRUD, such as automated form generation,
+validation of user-entered data, and automated transfer of data between
+forms and model objects.
+
+In keeping with the Catalyst (and Perl) spirit of flexibility, there are
+many different ways to approach advanced CRUD operations in a Catalyst
+environment.  One alternative is to use
+L<Catalyst::Helper::Controller::Scaffold|Catalyst::Helper::Controller::Scaffold> 
+to instantly construct a set of Controller methods and templates for 
+basic CRUD operations.  Although a popular subject in Quicktime 
+movies that serve as promotional material for various frameworks, 
+real-world applications generally require more control.  Other 
+options include L<Data::FormValidator|Data::FormValidator> and
+L<HTML::FillInForm|HTML::FillInForm>.
+
+Here, we will make use of the L<HTML::Widget|HTML::Widget> to not only 
+ease form creation, but to also provide validation of the submitted 
+data.  The approached used by this part of the tutorial is to slowly 
+incorporate additional L<HTML::Widget|HTML::Widget> functionality in a 
+step-wise fashion (we start with fairly simple form creation and then 
+move on to more complex and "magical" features such as validation and 
+auto-population/auto-saving).
+
+B<Note:> Part 8 of the tutorial is optional.  Users who do not wish to
+use L<HTML::Widget|HTML::Widget> may skip this part.
+
+You can checkout the source code for this example from the catalyst subversion repository as per the instructions in L<Catalyst::Manual::Tutorial::Intro>
+
+=head1 C<HTML::WIDGET> FORM CREATION
+
+This section looks at how L<HTML::Widget|HTML::Widget> can be used to
+add additional functionality to the manually created form from Part 3.
+
+=head2 Add the C<HTML::Widget> Plugin
+
+Open C<lib/MyApp.pm> in your editor and add the following to the list of
+plugins (be sure to leave the existing plugins enabled):
+
+    HTML::Widget
+
+=head2 Add a Form Creation Helper Method
+
+Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
+following method:
+
+    =head2 make_book_widget
+    
+    Build an HTML::Widget form for book creation and updates
+    
+    =cut
+    
+    sub make_book_widget {
+        my ($self, $c) = @_;
+    
+        # Create an HTML::Widget to build the form
+        my $w = $c->widget('book_form')->method('post');
+    
+        # Get authors
+        my @authorObjs = $c->model("MyAppDB::Author")->all();
+        my @authors = map {$_->id => $_->last_name }
+                           sort {$a->last_name cmp $b->last_name} @authorObjs;
+    
+        # Create the form feilds
+        $w->element('Textfield', 'title'  )->label('Title')->size(60);
+        $w->element('Textfield', 'rating' )->label('Rating')->size(1);
+        $w->element('Select',    'authors')->label('Authors')
+            ->options(@authors);
+        $w->element('Submit',    'submit' )->value('submit');
+    
+        # Return the widget    
+        return $w;
+    }
+
+This method provides a central location that builds an 
+HTML::Widget-based form with the appropriate fields.  The "Get authors" 
+code uses DBIC to retrieve a list of model objects and then uses C<map> 
+to create a hash where the hash keys are the database primary keys from 
+the authors table and the associated values are the last names of the 
+authors.
+
+=head2 Add Actions to Display and Save the Form
+
+Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
+following methods:
+
+    =head2 hw_create
+    
+    Build an HTML::Widget form for book creation and updates
+    
+    =cut
+    
+    sub hw_create : Local {
+        my ($self, $c) = @_;
+    
+        # Create the widget and set the action for the form
+        my $w = $self->make_book_widget($c);
+        $w->action($c->uri_for('hw_create_do'));
+    
+        # Write form to stash variable for use in template
+        $c->stash->{widget_result} = $w->result;
+    
+        # Set the template
+        $c->stash->{template} = 'books/hw_form.tt2';
+    }
+    
+    
+    =head2 hw_create_do
+    
+    Build an HTML::Widget form for book creation and updates
+    
+    =cut
+    
+    sub hw_create_do : Local {
+        my ($self, $c) = @_;
+    
+        # Retrieve the data from the form
+        my $title   = $c->request->params->{title};
+        my $rating  = $c->request->params->{rating};
+        my $authors = $c->request->params->{authors};
+    
+        # Call create() on the book model object. Pass the table 
+        # columns/field values we want to set as hash values
+        my $book = $c->model('MyAppDB::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 => $authors});
+        
+        # Set a status message for the user
+        $c->stash->{status_msg} = 'Book created';
+    
+        # Use 'hw_create' to redisplay the form.  As discussed in 
+        # Part 3, 'detach' is like 'forward', but it does not return
+        $c->detach('hw_create');
+    }
+
+Note how we use C<make_book_widget> to build the core parts of the form
+in one location, but we set the action (the URL the form is sent to when
+the user clicks the 'Submit' button) separately in C<hw_create>.  Doing
+so allows us to have the same form submit the data to different actions
+(e.g., C<hw_create_do> for a create operation but C<hw_update_do> to
+update an existing book object).
+
+B<NOTE:> If you receive an error about Catalyst not being able to find
+the template C<hw_create_do.tt2>, please verify that you followed the
+instructions in the final section of
+L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics> where
+you returned to a manually-specified template.  You can either use 
+C<forward>/C<detach> B<OR> default template names, but the two cannot
+be used together.
+
+
+=head2 Update the CSS
+
+Edit C<root/src/ttsite.css> and add the following lines to the bottom of
+the file:
+
+    label {
+        display: block;
+        width: 10em;
+        position: relative;
+        margin: .5em 0em;
+    }
+    label input {
+        position: absolute;
+        left: 100%;
+    }
+    label select {
+        position: absolute;
+        left: 100%;
+    }
+    .submit {
+        margin-top: 2em;;
+    }
+    .error_messages {
+        color: [% site.col.error %];
+    }
+
+These changes will display form elements vertically and also show error
+messages in red.  Note that we are pulling the color scheme settings
+from the C<root/lib/config/col> file that was created by the TTSite
+helper.  This allows us to change the color used by various error styles
+in the CSS from a single location.
+
+=head2 Create a Template Page To Display The Form
+
+Open C<root/src/books/hw_form.tt2> in your editor and enter the following:
+
+    [% META title = 'Create/Update Book' %]
+    
+    [% widget_result.as_xml %]
+    
+    <p><a href="[% Catalyst.uri_for('list') %]">Return to book list</a></p>
+
+=head2 Add Links for Create and Update via C<HTML::Widget>
+
+Open C<root/src/books/list.tt2> in your editor and add the following to
+the bottom of the existing file:
+
+    <p>
+      HTML::Widget:
+      <a href="[% Catalyst.uri_for('hw_create') %]">Create</a>
+    </p>
+
+
+=head2 Test The <HTML::Widget> Create Form
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still
+running) and restart it:
+
+    $ script/myapp_server.pl
+
+Login as C<test01>.  Once at the Book List page, click the HTML::Widget
+"Create" link to display for form produced by C<make_book_widget>.  Fill
+out the form with the following values: Title = "Internetworking with
+TCP/IP Vol. II", Rating = "4", and Author = "Comer".  Click Submit, and
+you will be returned to the Create/Update Book page with a "Book
+created" status message displayed.  Click "Return to book list" to view
+the newly created book on the main list.
+
+Also note that this implementation allows you to can create books with
+bogus information.  Although we have constrained the authors with the
+drop-down list, 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 (you can use any number you want, and even non-numeric values
+with SQLite).  The next section will address this concern.
+
+B<Note:> Depending on the database you are using and how you established
+the columns in your tables, the database could obviously provide various
+levels of "type enforcement" on your data.  The key point being made in
+the previous paragraph is that the I<web application> itself is not
+performing any validation.
+
+=head1 C<HTML::WIDGET> VALIDATION AND FILTERING
+
+Although the use of L<HTML::Widget|HTML::Widget> in the previous section
+did provide an automated mechanism to build the form, the real power of
+this module stems from functionality that can automatically validate and
+filter the user input.  Validation uses constraints to be sure that
+users input appropriate data (for example, that the email field of a
+form contains a valid email address).  Filtering can be used to remove
+extraneous whitespace from fields or to escape meta-characters in user
+input.
+
+=head2 Add Constraints and Filters to the Widget Creation Method
+
+Open C<lib/MyApp/Controller/Books.pm> in your editor and update the
+C<make_book_widget> method to match the following (new sections have
+been marked with a C<*** NEW:> comment):
+
+    sub make_book_widget {
+        my ($self, $c) = @_;
+    
+        # Create an HTML::Widget to build the form
+        my $w = $c->widget('book_form')->method('post');
+            
+        # Get authors
+        my @authorObjs = $c->model("MyAppDB::Author")->all();
+        my @authors = map {$_->id => $_->last_name }
+                           sort {$a->last_name cmp $b->last_name} @authorObjs;
+    
+        # Create the form feilds
+        $w->element('Textfield', 'title'  )->label('Title')->size(60);
+        $w->element('Textfield', 'rating' )->label('Rating')->size(1);
+        # ***NEW: Convert to multi-select list
+        $w->element('Select',    'authors')->label('Authors')
+            ->options(@authors)->multiple(1)->size(3);
+        $w->element('Submit',    'submit' )->value('submit');
+    
+        # ***NEW: Set constraints
+        $w->constraint(All     => qw/title rating authors/)
+            ->message('Required. ');
+        $w->constraint(Integer => qw/rating/)
+            ->message('Must be an integer. ');
+        $w->constraint(Range   => qw/rating/)->min(1)->max(5)
+            ->message('Must be a number between 1 and 5. ');
+        $w->constraint(Length  => qw/title/)->min(5)->max(50)
+            ->message('Must be between 5 and 50 characters. ');
+    
+        # ***NEW: Set filters
+        for my $column (qw/title rating authors/) {
+            $w->filter( HTMLEscape => $column );
+            $w->filter( TrimEdges  => $column );
+        }
+    
+        # Return the widget    
+        return $w;
+    }
+
+The main changes are:
+
+=over 4
+
+=item *
+
+The C<Select> element for C<authors> is changed from a single-select
+drop-down to a multi-select list by adding calls to C<multiple> (set to
+C<true>) and C<size> (set to the number of rows to display).
+
+=item *
+
+Four sets of constraints are added to provide validation of the user input.
+
+=item *
+
+Two filters are run on every field to remove and escape unwanted input.
+
+=back
+
+=head2 Rebuild the Form Submission Method to Include Validation
+
+Edit C<lib/MyApp/Controller/Books.pm> and change C<hw_create_do> to
+match the following code (enough of the code is different that you
+probably want to cut and paste this over code the existing method):
+
+    sub hw_create_do : Local {
+        my ($self, $c) = @_;
+    
+        # Retrieve the data from the form
+        my $title   = $c->request->params->{title};
+        my $rating  = $c->request->params->{rating};
+        my $authors = $c->request->params->{authors};
+        
+        # Create the widget and set the action for the form
+        my $w = $self->make_book_widget($c);
+        $w->action($c->uri_for('hw_create_do'));
+    
+        # Validate the form parameters
+        my $result = $w->process($c->req);
+    
+        # Write form (including validation error messages) to
+        # stash variable for use in template
+        $c->stash->{widget_result} = $result;
+    
+        # Were their validation errors?
+        if ($result->has_errors) {
+            # Warn the user at the top of the form that there were errors.
+            # Note that there will also be per-field feedback on
+            # validation errors because of '$w->process($c->req)' above.
+            $c->stash->{error_msg} = 'Validation errors!';
+        } else {
+            # Everything validated OK, so do the create
+            # Call create() on the book model object. Pass the table
+            # columns/field values we want to set as hash values
+            my $book = $c->model('MyAppDB::Book')->create({
+                    title   => $title,
+                    rating  => $rating
+                });
+    
+            # Add a record to the join table for this book, mapping to
+            # appropriate author.  Note that $authors will be 1 author as
+            # a scalar or ref to list of authors depending on how many the
+            # user selected; the 'ref $authors ?...' handles both cases
+            foreach my $author (ref $authors ? @$authors : $authors) {
+                $book->add_to_book_authors({author_id => $author});
+            }    
+            # Set a status message for the user
+            $c->stash->{status_msg} = 'Book created';
+        }
+    
+        # Set the template
+        $c->stash->{template} = 'books/hw_form.tt2';
+    }
+
+The key changes to C<hw_create_do> are:
+
+=over 4
+
+=item *
+
+C<hw_create_do> no longer does a C<detach> to C<hw_create> to redisplay
+the form.  Now that C<hw_create_do> has to process the form in order to
+perform the validation, we go ahead and build a complete set of form
+presentation logic into C<hw_create_do> (for example, C<hw_create_do>
+now has a C<$c-E<gt>stash-E<gt>{template}> line).  Note that if we
+process the form in C<hw_create_do> I<and> forward/detach back to
+<hw_create>, we would end up with C<make_book_widget> being called
+twice, resulting in a duplicate set of elements being added to the form.
+(There are other ways to address the "duplicate form rendering" issue --
+just be aware that it exists.)
+
+=item *
+
+C<$w-E<gt>process($c-E<gt>req)> is called to run the validation logic.
+Not only does this set the C<has_errors> flag if validation errors are
+encountered, it returns a string containing any field-specific warning
+messages.
+
+=item *
+
+An C<if> statement checks if any validation errors were encountered.  If
+so, C<$c-E<gt>stash-E<gt>{error_msg}> is set and the input form is
+redisplayed.  If no errors were found, the object is created in a manner
+similar to the prior version of the C<hw_create_do> method.
+
+=back
+
+=head2 Try Out the Form
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still 
+running) and restart it:
+
+    $ script/myapp_server.pl
+
+Now try adding a book with various errors: title less than 5 characters,
+non-numeric rating, a rating of 0 or 6, etc.  Also try selecting one,
+two, and zero authors.  When you click Submit, the HTML::Widget
+C<constraint> items will validate the logic and insert feedback as
+appropriate.
+
+
+=head1 Enable C<DBIx::Class::HTMLWidget> Support
+
+In this section we will take advantage of some of the "auto-population"
+features of C<DBIx::Class::HTMLWidget>.  Enabling
+C<DBIx::Class::HTMLWidget> provides two additional methods to your DBIC
+model classes:
+
+=over 4
+
+=item *
+
+fill_widget()
+
+Takes data from the database and transfers it to your form widget.
+
+=item *
+
+populate_from_widget()
+
+Takes data from a form widget and uses it to update the corresponding
+records in the database.
+
+=back
+
+In other words, the two methods are a mirror image of each other: one
+reads from the database while the other writes to the database.
+
+=head2 Add C<DBIx::Class::HTMLWidget> to DBIC Model
+
+In order to use L<DBIx::Class::HTMLWidget|DBIx::Class::HTMLWidget>, we
+need to add C<HTMLWidget> to the C<load_components> line of DBIC result
+source files that need to use the C<fill_widget> and
+C<populate_from_widget> methods.  In this case, open
+C<lib/MyAppDB/Book.pm> and update the C<load_components> line to match:
+
+       __PACKAGE__->load_components(qw/PK::Auto Core HTMLWidget/);
+
+=head2 Use C<populate_from_widget> in C<hw_create_do>
+
+Edit C<lib/MyApp/Controller/Books.pm> and update C<hw_create_do> to
+match the following code:
+
+    =head2 hw_create_do
+    
+    Build an HTML::Widget form for book creation and updates
+    
+    =cut
+    
+    sub hw_create_do : Local {
+        my ($self, $c) = @_;
+    
+        # Create the widget and set the action for the form
+        my $w = $self->make_book_widget($c);
+        $w->action($c->uri_for('hw_create_do'));
+    
+        # Validate the form parameters
+        my $result = $w->process($c->req);
+    
+        # Write form (including validation error messages) to
+        # stash variable for use in template
+        $c->stash->{widget_result} = $result;
+    
+        # Were their validation errors?
+        if ($result->has_errors) {
+            # Warn the user at the top of the form that there were errors.
+            # Note that there will also be per-field feedback on
+            # validation errors because of '$w->process($c->req)' above.
+            $c->stash->{error_msg} = 'Validation errors!';
+        } else {
+            my $book = $c->model('MyAppDB::Book')->new({});
+            $book->populate_from_widget($result);
+    
+            # Add a record to the join table for this book, mapping to
+            # appropriate author.  Note that $authors will be 1 author as
+            # a scalar or ref to list of authors depending on how many the
+            # user selected; the 'ref $authors ?...' handles both cases
+            my $authors = $c->request->params->{authors};
+            foreach my $author (ref $authors ? @$authors : $authors) {
+                $book->add_to_book_authors({author_id => $author});
+            }
+    
+            # Set a status message for the user
+            $c->flash->{status_msg} = 'Book created';
+            
+            # Redisplay an empty form for another
+            $c->stash->{widget_result} = $w->result;
+        }
+    
+        # Set the template
+        $c->stash->{template} = 'books/hw_form.tt2';
+    }
+
+In this version of C<hw_create_do> we removed the logic that manually
+pulled the form variables and used them to call
+C<$c-E<gt>model('MyAppDB::Book')-E<gt>create> and replaced it with a
+single call to C<$book-E<gt>populate_from_widget>.  Note that we still
+have to call C<$book-E<gt>add_to_book_authors> once per author because
+C<populate_from_widget> does not currently handle the relationships
+between tables.  Also, we reset the form to an empty fields by adding
+another call to C<$w-E<gt>result> and storing the output in the stash 
+(if we don't override the output from C<$w-E<gt>process($c-E<gt>req)>,
+the form values already entered will be retained on redisplay --
+although this could be desirable for some applications, we avoid it
+here to help avoid the creation of duplicate records).
+
+
+=head2 Try Out the Form
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still 
+running) and restart it:
+
+    $ script/myapp_server.pl
+
+Try adding a book that validates.  Return to the book list and the book 
+you added should be visible.
+
+
+
+=head1 Rendering C<HTMLWidget> Forms in a Table
+
+Some developers my wish to use the "old-fashioned" table style of 
+rendering a form in lieu of the default C<HTML::Widget> rendering that 
+assumes you will use CSS for formatting.  This section demonstrates
+some techniques that can override the default rendering with a 
+custom class.
+
+
+=head2 Add a New "Element Container"
+
+Open C<lib/FormElementContainer.pm> in your editor and enter:
+
+    package FormElementContainer;
+    
+    use base 'HTML::Widget::Container';
+    
+    sub _build_element {
+        my ($self, $element) = @_;
+    
+        return () unless $element;
+        if (ref $element eq 'ARRAY') {
+            return map { $self->_build_element($_) } @{$element};
+        }
+        my $e = $element->clone;
+        $e = new HTML::Element('span', class => 'fields_with_errors')->push_content($e)
+            if $self->error && $e->tag eq 'input';
+    
+        return $e ? ($e) : ();
+    }
+    
+    1;
+
+This simply dumps the HTML code for a given form element, followed by a 
+C<span> that can contain validation error message.
+
+
+=head2 Enable the New Element Container When Building the Form
+
+Open C<lib/MyApp/Controller/Books.pm> in your editor.  First add a
+C<use> for your element container class:
+
+    use FormElementContainer;
+
+B<Note:> If you forget to C<use> your container class in your 
+controller, then your form will not be displayed and no error messages 
+will be generated. Don't forget this important step!
+
+Then tell C<HTML::Widget> to use that class during rendering by updating
+C<make_book_widget> to match the following:
+
+    sub make_book_widget {
+        my ($self, $c) = @_;
+    
+        # Create an HTML::Widget to build the form
+        my $w = $c->widget('book_form')->method('post');
+    
+        # ***New: Use custom class to render each element in the form    
+        $w->element_container_class('FormElementContainer');
+        
+        # Get authors
+        my @authorObjs = $c->model("MyAppDB::Author")->all();
+        my @authors = map {$_->id => $_->last_name }
+                           sort {$a->last_name cmp $b->last_name} @authorObjs;
+    
+        # Create the form feilds
+        $w->element('Textfield', 'title'  )->label('Title')->size(60);
+        $w->element('Textfield', 'rating' )->label('Rating')->size(1);
+        # Convert to multi-select list
+        $w->element('Select',    'authors')->label('Authors')
+            ->options(@authors)->multiple(1)->size(3);
+        $w->element('Submit',    'submit' )->value('submit');
+    
+        # Set constraints
+        $w->constraint(All     => qw/title rating authors/)
+            ->message('Required. ');
+        $w->constraint(Integer => qw/rating/)
+            ->message('Must be an integer. ');
+        $w->constraint(Range   => qw/rating/)->min(1)->max(5)
+            ->message('Must be a number between 1 and 5. ');
+        $w->constraint(Length  => qw/title/)->min(5)->max(50)
+            ->message('Must be between 5 and 50 characters. ');
+    
+        # Set filters
+        for my $column (qw/title rating authors/) {
+            $w->filter( HTMLEscape => $column );
+            $w->filter( TrimEdges  => $column );
+        }
+    
+        # Return the widget    
+        return $w;
+    }
+
+The two new lines are marked with C<***New:>.
+
+
+=head2 Update the TT Template
+
+Open C<root/src/books/hw_form.tt2> and edit it to match:
+
+    [% META title = 'Create/Update Book' %]
+    
+    [%# Comment out the auto-rendered form %]
+    [%# widget_result.as_xml %]
+    
+    
+    [%# Iterate over the form elements and display each -%]
+    <form name="book_form" action="[% widget_result.action %]" method="post">
+    <table border="0">
+    [% FOREACH element = widget_result.elements %]
+      <tr>
+        <td class="form-label">
+          [% element.label.as_text %]
+        </td>
+        <td class="form-element">
+          [% element.element_xml %]
+          <span class="form-error">
+            [% element.error_xml %]
+          </span>
+        </td>
+      </tr>
+    [% END %]
+    </table>
+    </form>
+    
+    
+    <p><a href="[% Catalyst.uri_for('list') %]">Return to book list</a></p>
+    
+    
+    [%# A little JavaScript to move the cursor to the first field %]
+    <script LANGUAGE="JavaScript">
+    document.book_form.book_form_title.focus();
+    </script>
+
+This represents three changes:
+
+=over 4
+
+=item *
+
+The existing C<widget_result.as_xml> has been commented out.
+
+=item *
+
+It loops through each form element, displaying the field name in the 
+first table cell along with the form element and validation errors in 
+the second field.
+
+=item *
+
+JavaScript to position the user's cursor in the first field of the form.
+
+=back
+
+
+=head2 Try Out the Form
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still 
+running) and restart it:
+
+    $ script/myapp_server.pl
+
+Try adding a book that validates.  Return to the book list and the book 
+you added should be visible.
+
+
+=head1 AUTHOR
+
+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-Runtime/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
diff --git a/lib/Catalyst/Manual/Tutorial/Tutorial/Appendices.pod b/lib/Catalyst/Manual/Tutorial/Tutorial/Appendices.pod
new file mode 100644 (file)
index 0000000..1025640
--- /dev/null
@@ -0,0 +1,473 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::Appendices - Catalyst Tutorial - Part 9: Appendices
+
+
+=head1 OVERVIEW
+
+This is B<Part 9 of 9> of the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
+
+=item 3
+
+L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>
+
+=item 4
+
+L<Authentication|Catalyst::Manual::Tutorial::Authentication>
+
+=item 5
+
+L<Authorization|Catalyst::Manual::Tutorial::Authorization>
+
+=item 6
+
+L<Debugging|Catalyst::Manual::Tutorial::Debugging>
+
+=item 7
+
+L<Testing|Catalyst::Manual::Tutorial::Testing>
+
+=item 8
+
+L<AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
+
+=item 9
+
+B<Appendices>
+
+=back
+
+
+=head1 DESCRIPTION
+
+This part of the tutorial provides supporting information relevant to
+the Catalyst tutorial.
+
+
+=head1 APPENDIX 1: CUT AND PASTE FOR POD-BASED EXAMPLES
+
+You may notice that Pod indents example code with four spaces.  This
+section provides some quick advice to "un-indent" this text in common
+editors.
+
+=head2 "Un-indenting" with Vi/Vim
+
+When cutting and pasting multi-line text from Pod-based documents, the
+following vi/vim regexs can be helpful to "un-indent" the inserted text
+(do NOT type the quotes, they are only included to show spaces in the
+regex patterns).  I<Note that all 3 of the regexs end in 4 spaces>:
+
+=over 4
+
+=item * 
+
+":0,$s/^    "
+
+Removes four leading spaces from the entire file (from the first line,
+C<0>, to the last line, C<$>).
+
+=item * 
+
+"%s/^    "
+
+A shortcut for the previous item (C<%> specifies the entire file; so
+this removes four leading spaces from every line).
+
+=item * 
+
+":.,$s/^    "
+
+Removes the first four spaces from the line the cursor is on at the time
+the regex command is executed (".") to the last line of the file.
+
+=item * 
+
+":.,44s/^    "
+
+Removes four leading space from the current line through line 44
+(obviously adjust the C<44> to the appropriate value in your example).
+
+=back
+
+=head2 "Un-indenting" with Emacs
+
+Although there author has not used emacs for many years (apologies to 
+the emacs fans out there), here is a quick hint to get you started.  To
+replace the leading spaces of every line in a file, use:
+
+    M-x replace-regexp<RET>
+    Replace regexp: ^    <RET>
+    with: <RET>
+
+All of that will occur on the single line at the bottom of your screen.
+Note that "<RET>" represents the return key/enter.  Also, there are 
+four spaces after the "^" on the "Replace regexp:" line and no spaces
+entered on the last line.
+
+You can limit the replacement operation by selecting text first (depending
+on your version of emacs, you can either use the mouse or experiment with 
+commands such as C<C-SPC> to set the mark at the cursor location and 
+C<C-E<lt>> and C<C-E<gt>> to set the mark at the beginning and end of the
+file respectively.
+
+
+=head1 APPENDIX 2: USING MYSQL AND POSTGRESQL
+
+The main database used in this tutorial is the very simple yet powerful
+SQLite.  This section provides information that can be used to "convert"
+the tutorial to use MySQL and PostgreSQL.  However, note that part of
+the beauty of the MVC architecture is that very little database-specific
+code is spread throughout the system (at least when MVC is "done
+right").  Consequently, converting from one database to another is
+relatively painless with most Catalyst applications.  In general, you
+just need to adapt the schema definition C<.sql> file you use to
+initialize your database and adjust a few configuration parameters.
+
+Also note that the purpose of the data definition statements for this
+section are not designed to take maximum advantage of the various
+features in each database for issues such as referential integrity and
+field types/constraints.
+
+=head2 MySQL
+
+Use the following steps to adapt the tutorial to MySQL.  Thanks to Jim 
+Howard for the help.
+
+=over 4
+
+=item *
+
+Part 2: Catalyst Basics
+
+=over 4
+
+=item *
+
+Install the required software:
+
+=over 4
+
+=item *
+
+The MySQL database server and client utility.
+
+=item *
+
+The Perl C<DBD::MySQL> module
+
+=back
+
+For CentOS users (see 
+L<Catalyst::Manual::Installation::CentOS4|Catalyst::Manual::Installation::CentOS4>),
+you can use the following commands to install the software and start the MySQL
+daemon:
+
+    yum -y install mysql mysql-server
+    service mysqld start
+
+=item *
+
+Create the database and set the permissions:
+
+    $ mysql
+    Welcome to the MySQL monitor.  Commands end with ; or \g.
+    Your MySQL connection id is 2 to server version: 4.1.20
+    
+    Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
+    
+    mysql> create database myapp;
+    Query OK, 1 row affected (0.01 sec)
+    
+    mysql> grant all on myapp.* to tutorial@'localhost';
+    Query OK, 0 rows affected (0.00 sec)
+    
+    mysql> flush privileges;
+    Query OK, 0 rows affected (0.00 sec)
+    
+    mysql> quit
+    Bye
+
+=item *
+
+Create the C<.sql> file and load the data:
+
+=over 4
+
+=item *
+
+Open the C<myapp01_mysql.sql> in your editor and enter:
+
+    --
+    -- Create a very simple database to hold book and author information
+    --
+    DROP TABLE IF EXISTS books;
+    DROP TABLE IF EXISTS book_authors;
+    DROP TABLE IF EXISTS authors;
+    CREATE TABLE books (
+           id          INT(11) PRIMARY KEY AUTO_INCREMENT,
+           title       TEXT ,
+           rating      INT(11)
+    );
+    -- 'book_authors' is a many-to-many join table between books & authors
+    CREATE TABLE book_authors (
+           book_id     INT(11),
+           author_id   INT(11),
+           PRIMARY KEY (book_id, author_id)
+    );
+    CREATE TABLE authors (
+           id          INT(11) PRIMARY KEY AUTO_INCREMENT,
+           first_name  TEXT,
+           last_name   TEXT
+    );
+    ---
+    --- 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);
+
+=item *
+
+Load the data:
+
+    mysql -ututorial myapp < myapp01_mysql.sql
+
+=item *
+
+Make sure the data loaded correctly:
+
+    $ mysql -ututorial myapp
+    Reading table information for completion of table and column names
+    You can turn off this feature to get a quicker startup with -A
+    
+    Welcome to the MySQL monitor.  Commands end with ; or \g.
+    Your MySQL connection id is 4 to server version: 4.1.20
+    
+    Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
+    
+    mysql> show tables;
+    +-----------------+
+    | Tables_in_myapp |
+    +-----------------+
+    | authors         |
+    | book_authors    |
+    | books           |
+    +-----------------+
+    3 rows in set (0.00 sec)
+    
+    mysql> select * from books;
+    +----+------------------------------------+--------+
+    | id | title                              | rating |
+    +----+------------------------------------+--------+
+    |  1 | CCSP SNRS Exam Certification Guide |      5 |
+    |  2 | TCP/IP Illustrated, Volume 1       |      5 |
+    |  3 | Internetworking with TCP/IP Vol.1  |      4 |
+    |  4 | Perl Cookbook                      |      5 |
+    |  5 | Designing with Web Standards       |      5 |
+    +----+------------------------------------+--------+
+    5 rows in set (0.00 sec)
+    
+    mysql>
+
+=back
+
+=item *
+
+Update the model:
+
+=over 4
+
+=item *
+
+Delete the existing model:
+
+    rm lib/MyApp/Model/MyAppDB.pm
+
+=item *
+
+Regenerate the model using the Catalyst "_create.pl" script:
+
+    script/myapp_create.pl model MyAppDB DBIC::Schema MyAppDB dbi:mysql:myapp 'tutorial' '' '{ AutoCommit => 1 }'
+
+=back
+
+=back
+
+=item *
+
+Part 4: Authentication
+
+=over 4
+
+=item *
+
+Create the C<.sql> file for the user/roles data:
+
+Open C<myapp02_mysql.sql> in your editor and enter:
+
+    --
+    -- Add users and roles tables, along with a many-to-many join table
+    --
+    CREATE TABLE users (
+            id            INT(11) PRIMARY KEY,
+            username      TEXT,
+            password      TEXT,
+            email_address TEXT,
+            first_name    TEXT,
+            last_name     TEXT,
+            active        INT(11)
+    );
+    CREATE TABLE roles (
+            id   INTEGER PRIMARY KEY,
+            role TEXT
+    );
+    CREATE TABLE user_roles (
+            user_id INT(11),
+            role_id INT(11),
+            PRIMARY KEY (user_id, role_id)
+    );
+    --
+    -- 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);
+
+=item *
+
+Load the user/roles data:
+
+    mysql -ututorial myapp < myapp02_mysql.sql
+
+=item *
+
+Create the C<.sql> file for the hashed password data:
+
+Open C<myapp03_mysql.sql> in your editor and enter:
+
+    --
+    -- Convert passwords to SHA-1 hashes
+    --
+    UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 1;
+    UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 2;
+    UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 3;
+
+=item *
+
+Load the user/roles data:
+
+    mysql -ututorial myapp < myapp03_mysql.sql
+
+=back
+
+=back
+
+=head2 PostgreSQL
+
+B<TODO> -- Please see the latest version of this document for possible updates:
+L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Runtime/lib/Catalyst/Manual/Tutorial/Appendices.pod>
+
+
+=head1 APPENDIX 3: IMPROVED HASHING SCRIPT
+
+Here is an improved SHA-1 hashing script from Gavin Henry that does
+not expose the passwords to "capture" on the command line.
+
+    #!/usr/bin/perl -w
+    #===============================================================================
+    #
+    #         FILE:  enc_pass.pl
+    #
+    #        USAGE:  ./enc_pass.pl
+    #
+    #  DESCRIPTION:  Encrypt a Password using SHA-1
+    #
+    #      OPTIONS:  ---
+    # REQUIREMENTS:  ---
+    #         BUGS:  ---
+    #        NOTES:  ---
+    #       AUTHOR:  Gavin Henry (GH), <ghenry@suretecsystems.com>
+    #      COMPANY:  Suretec Systems Ltd.
+    #      VERSION:  1.0
+    #      CREATED:  26/06/2006
+    #     REVISION:  ---
+    #    COPYRIGHT:  http://search.cpan.org/dist/perl/pod/perlgpl.pod
+    #===============================================================================
+    
+    use strict;
+    use warnings;
+    use Digest::SHA1;
+    use Term::ReadKey;
+    
+    sub get_pass {
+        ReadMode 'noecho';
+        chomp( my $pw = ReadLine 0 );
+        ReadMode 'normal';
+        return $pw;
+    }
+    
+    print "Enter the password to be encrypted: ";
+    my $pass = get_pass();
+    
+    print "\nConfirm the password: ";
+    my $verify = get_pass();
+    
+    if ( $pass eq $verify ) {
+        my $sha1_enc = Digest::SHA1->new;
+        $sha1_enc->add($pass);
+    
+        print "\nYour encrypted password is: "
+          . $sha1_enc->hexdigest . "\n"
+          . "Paste this into your SQL INSERT/COPY Data.\n";
+    }
+    else {
+        print "\nPasswords do not match!\n";
+    }
+
+
+=head1 AUTHOR
+
+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-Runtime/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
diff --git a/lib/Catalyst/Manual/Tutorial/Tutorial/Authentication.pod b/lib/Catalyst/Manual/Tutorial/Tutorial/Authentication.pod
new file mode 100644 (file)
index 0000000..46b061c
--- /dev/null
@@ -0,0 +1,875 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::Authentication - Catalyst Tutorial - Part 4: Authentication
+
+
+=head1 OVERVIEW
+
+This is B<Part 4 of 9> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
+
+=item 3
+
+L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
+
+=item 4
+
+B<Authentication>
+
+=item 5
+
+L<Authorization|Catalyst::Manual::Tutorial::Authorization>
+
+=item 6
+
+L<Debugging|Catalyst::Manual::Tutorial::Debugging>
+
+=item 7
+
+L<Testing|Catalyst::Manual::Tutorial::Testing>
+
+=item 8
+
+L<AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
+
+=item 9
+
+L<Appendices|Catalyst::Manual::Tutorial::Appendices>
+
+=back
+
+
+=head1 DESCRIPTION
+
+Now that we finally have a simple yet functional application, we can
+focus on providing authentication (with authorization coming next in
+Part 5).
+
+This part of the tutorial is divided into two main sections: 1) basic,
+cleartext authentication and 2) hash-based authentication.
+
+You can checkout the source code for this example from the catalyst
+subversion repository as per the instructions in
+L<Catalyst::Manual::Tutorial::Intro>
+
+=head1 BASIC AUTHENTICATION
+
+This section explores how to add authentication logic to a Catalyst
+application.
+
+
+=head2 Add Users and Roles to the Database
+
+First, we add both user and role information to the database (we will
+add the role information here although it will not be used until the
+authorization section, Part 5).  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
+    --
+    CREATE TABLE users (
+            id            INTEGER PRIMARY KEY,
+            username      TEXT,
+            password      TEXT,
+            email_address TEXT,
+            first_name    TEXT,
+            last_name     TEXT,
+            active        INTEGER
+    );
+    CREATE TABLE roles (
+            id   INTEGER PRIMARY KEY,
+            role TEXT
+    );
+    CREATE TABLE user_roles (
+            user_id INTEGER,
+            role_id INTEGER,
+            PRIMARY KEY (user_id, role_id)
+    );
+    --
+    -- 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);
+
+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
+
+This step adds DBIC-based classes for the user-related database tables
+(the role information will not be used until Part 5):
+
+Edit C<lib/MyAppDB.pm> and update the contents to match (only the
+C<MyAppDB =E<gt> [qw/Book BookAuthor Author User UserRole Role/]> line
+has changed):
+
+    package MyAppDB;
+    
+    =head1 NAME 
+    
+    MyAppDB -- DBIC Schema Class
+    
+    =cut
+    
+    # Our schema needs to inherit from 'DBIx::Class::Schema'
+    use base qw/DBIx::Class::Schema/;
+    
+    # Need to load the DB Model classes here.
+    # You can use this syntax if you want:
+    #    __PACKAGE__->load_classes(qw/Book BookAuthor Author User UserRole Role/);
+    # Also, if you simply want to load all of the classes in a directory
+    # of the same name as your schema class (as we do here) you can use:
+    #    __PACKAGE__->load_classes(qw//);
+    # But the variation below is more flexible in that it can be used to 
+    # load from multiple namespaces.
+    __PACKAGE__->load_classes({
+        MyAppDB => [qw/Book BookAuthor Author User UserRole Role/]
+    });
+    
+    1;
+
+
+=head2 Create New "Result Source Objects"
+
+Create the following three files with the content shown below.
+
+C<lib/MyAppDB/User.pm>:
+
+    package MyAppDB::User;
+    
+    use base qw/DBIx::Class/;
+    
+    # Load required DBIC stuff
+    __PACKAGE__->load_components(qw/PK::Auto Core/);
+    # Set the table name
+    __PACKAGE__->table('users');
+    # Set columns in table
+    __PACKAGE__->add_columns(qw/id username password email_address first_name last_name/);
+    # Set the primary key for the table
+    __PACKAGE__->set_primary_key('id');
+    
+    #
+    # Set relationships:
+    #
+    
+    # has_many():
+    #   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
+    __PACKAGE__->has_many(map_user_role => 'MyAppDB::UserRole', 'user_id');
+    
+    
+    =head1 NAME
+    
+    MyAppDB::User - A model object representing a person with access to the system.
+    
+    =head1 DESCRIPTION
+    
+    This is an object that represents a row in the 'users' table of your application
+    database.  It uses DBIx::Class (aka, DBIC) to do ORM.
+    
+    For Catalyst, this is designed to be used through MyApp::Model::MyAppDB.
+    Offline utilities may wish to use this class directly.
+    
+    =cut
+    
+    1;
+
+
+C<lib/MyAppDB/Role.pm>:
+
+    package MyAppDB::Role;
+    
+    use base qw/DBIx::Class/;
+    
+    # Load required DBIC stuff
+    __PACKAGE__->load_components(qw/PK::Auto Core/);
+    # Set the table name
+    __PACKAGE__->table('roles');
+    # Set columns in table
+    __PACKAGE__->add_columns(qw/id role/);
+    # Set the primary key for the table
+    __PACKAGE__->set_primary_key('id');
+    
+    #
+    # Set relationships:
+    #
+    
+    # has_many():
+    #   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
+    __PACKAGE__->has_many(map_user_role => 'MyAppDB::UserRole', 'role_id');
+    
+    
+    =head1 NAME
+    
+    MyAppDB::Role - A model object representing a class of access permissions to 
+    the system.
+    
+    =head1 DESCRIPTION
+    
+    This is an object that represents a row in the 'roles' table of your 
+    application database.  It uses DBIx::Class (aka, DBIC) to do ORM.
+    
+    For Catalyst, this is designed to be used through MyApp::Model::MyAppDB.
+    "Offline" utilities may wish to use this class directly.
+    
+    =cut
+    
+    1;
+
+
+C<lib/MyAppDB/UserRole.pm>:
+
+    package MyAppDB::UserRole;
+    
+    use base qw/DBIx::Class/;
+    
+    # Load required DBIC stuff
+    __PACKAGE__->load_components(qw/PK::Auto Core/);
+    # Set the table name
+    __PACKAGE__->table('user_roles');
+    # Set columns in table
+    __PACKAGE__->add_columns(qw/user_id role_id/);
+    # Set the primary key for the table
+    __PACKAGE__->set_primary_key(qw/user_id role_id/);
+    
+    #
+    # Set relationships:
+    #
+    
+    # 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(user => 'MyAppDB::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 => 'MyAppDB::Role', 'role_id');
+    
+    
+    =head1 NAME
+    
+    MyAppDB::UserRole - A model object representing the JOIN between Users and Roles.
+    
+    =head1 DESCRIPTION
+    
+    This is an object that represents a row in the 'user_roles' table of your application
+    database.  It uses DBIx::Class (aka, DBIC) to do ORM.
+    
+    You probably won't need to use this class directly -- it will be automatically
+    used by DBIC where joins are needed.
+    
+    For Catalyst, this is designed to be used through MyApp::Model::MyAppDB.
+    Offline utilities may wish to use this class directly.
+    
+    =cut
+    
+    1;
+
+The code for these three result source classes is obviously very familiar to the C<Book>, C<Author>, and C<BookAuthor> classes created in Part 2.
+
+
+=head2 Sanity-Check Reload of Development Server
+
+We aren't ready to try out the authentication just yet; we only want to do a quick check to be sure our model loads correctly.  Press C<Ctrl-C> to kill the previous server instance (if it's still running) and restart it:
+
+    $ script/myapp_server.pl
+
+Look for the three new model objects in the startup debug output:
+
+    ...
+     .-------------------------------------------------------------------+----------.
+    | Class                                                             | Type     |
+    +-------------------------------------------------------------------+----------+
+    | MyApp::Controller::Books                                          | instance |
+    | MyApp::Controller::Root                                           | instance |
+    | MyApp::Model::MyAppDB                                             | instance |
+    | MyApp::Model::MyAppDB::Author                                     | class    |
+    | MyApp::Model::MyAppDB::Book                                       | class    |
+    | MyApp::Model::MyAppDB::BookAuthor                                 | class    |
+    | MyApp::Model::MyAppDB::Role                                       | class    |
+    | MyApp::Model::MyAppDB::User                                       | class    |
+    | MyApp::Model::MyAppDB::UserRole                                   | class    |
+    | MyApp::View::TT                                                   | instance |
+    '-------------------------------------------------------------------+----------'
+    ...
+
+Again, notice that your "result source" classes have been "re-loaded" by Catalyst under C<MyApp::Model>.
+
+
+=head2 Include Authentication and Session Plugins
+
+Edit C<lib/MyApp.pm> and update it as follows (everything below C<StackTrace> is new):
+
+    use Catalyst qw/
+            -Debug
+            ConfigLoader
+            Static::Simple
+            
+            StackTrace
+            
+            Authentication
+            Authentication::Store::DBIC
+            Authentication::Credential::Password
+            
+            Session
+            Session::Store::FastMmap
+            Session::State::Cookie
+            /;
+
+The three C<Authentication> plugins work together to support
+Authentication while the C<Session> plugins are required to maintain
+state across multiple HTTP requests.  Note that there are several
+options for L<Session::Store|Catalyst::Plugin::Session::Store> 
+(L<Session::Store::FastMmap|Catalyst::Plugin::Session::Store::FastMmap>
+is generally a good choice if you are on Unix; try
+L<Cache::FileCache|Catalyst::Plugin::Cache::FileCache> if you are on
+Win32) -- consult L<Session::Store|Catalyst::Plugin::Session::Store> and
+its subclasses for additional information.
+
+
+=head2 Configure Authentication
+
+Although C<__PACKAGE__-E<gt>config(name =E<gt> 'value');> is still
+supported, newer Catalyst applications tend to place all configuration
+information in C<myapp.yml> and automatically load this information into
+C<MyApp-E<gt>config> using the 
+L<ConfigLoader|Catalyst::Plugin::ConfigLoader> plugin.  Here, we need
+to load several parameters that tell 
+L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>
+where to locate information in your database.  To do this, edit the 
+C<myapp.yml> YAML and update it to match:
+
+    ---
+    name: MyApp
+    authentication:
+        dbic:
+            # Note this first definition would be the same as setting
+            # __PACKAGE__->config->{authentication}->{dbic}->{user_class} = 'MyAppDB::User'
+            # in lib/MyApp.pm (IOW, each hash key becomes a "name:" in the YAML file).
+            #
+            # This is the model object created by Catalyst::Model::DBIC from your
+            # schema (you created 'MyAppDB::User' but as the Catalyst startup
+            # debug messages show, it was loaded as 'MyApp::Model::MyAppDB::User').
+            # NOTE: Omit 'MyApp::Model' to avoid a component lookup issue in Catalyst 5.66
+            user_class: MyAppDB::User
+            # This is the name of the field in your 'users' table that contains the user's name
+            user_field: username
+            # This is the name of the field in your 'users' table that contains the password
+            password_field: password
+            # Other options can go here for hashed passwords
+
+Inline comments in the code above explain how each field is being used.
+
+B<TIP>: Although YAML uses a very simple and easy-to-ready format, it
+does require the use of a consistent level of indenting.  Be sure you
+line up everything on a given 'level' with the same number of indents.
+Also, be sure not to use C<tab> characters (YAML does not support them
+because they are handled inconsistently across editors).
+
+
+=head2 Add Login and Logout Controllers
+
+Use the Catalyst create script to create two stub controller files:
+
+    $ script/myapp_create.pl controller Login
+    $ script/myapp_create.pl controller Logout
+
+B<NOTE>: You could easily use a single controller here.  For example,
+you could have a C<User> controller with both C<login> and C<logout>
+actions.  Remember, Catalyst is designed to be very flexible, and leaves
+such matters up to you, the designer and programmer.
+
+Then open C<lib/MyApp/Controller/Login.pm>, locate the C<sub index : 
+Private> method (this was automatically inserted by the helpers when we 
+created the Login controller above), and delete this line:
+
+    $c->response->body('Matched MyApp::Controller::Login in Login.');
+
+Then update it to match:
+
+    =head2 index
+    
+    Login logic
+    
+    =cut
+    
+    sub index : Private {
+        my ($self, $c) = @_;
+    
+        # Get the username and password from form
+        my $username = $c->request->params->{username} || "";
+        my $password = $c->request->params->{password} || "";
+    
+        # If the username and password values were found in form
+        if ($username && $password) {
+            # Attempt to log the user in
+            if ($c->login($username, $password)) {
+                # If successful, then let them use the application
+                $c->response->redirect($c->uri_for('/books/list'));
+                return;
+            } else {
+                # Set an error message
+                $c->stash->{error_msg} = "Bad username or password.";
+            }
+        }
+    
+        # If either of above don't work out, send to the login page
+        $c->stash->{template} = 'login.tt2';
+    }
+
+This controller fetches the C<username> and C<password> values from the
+login form and attempts to perform a login.  If successful, it redirects
+the user to the book list page.  If the login fails, the user will stay
+at the login page but receive an error message.  If the C<username> and
+C<password> values are not present in the form, the user will be taken
+to the empty login form.
+
+Note that we could have used something like C<sub default :Private>; 
+however, the use of C<default> actions is discouraged because it does
+not receive path args as with other actions.  The recommended practice 
+is to only use C<default> in C<MyApp::Controller::Root>.
+
+Another option would be to use something like 
+C<sub base :Path :Args(0) {...}> (where the C<...> refers to the login 
+code shown in C<sub index : Private> above). We are using C<sub base 
+:Path :Args(0) {...}> here to specifically match the URL C</login>. 
+C<Path> actions (aka, "literal actions") create URI matches relative to 
+the namespace of the controller where they are defined.  Although 
+C<Path> supports arguments that allow relative and absolute paths to be 
+defined, here we use an empty C<Path> definition to match on just the 
+name of the controller itself.  The method name, C<base>, is arbitrary. 
+We make the match even more specific with the C<:Args(0)> action 
+modifier -- this forces the match on I<only> C</login>, not 
+C</login/somethingelse>.
+
+Next, update the corresponding method in C<lib/MyApp/Controller/Logout.pm>
+to match:
+
+    =head2 index
+    
+    Logout logic
+    
+    =cut
+    
+    sub index : Private {
+        my ($self, $c) = @_;
+    
+        # Clear the user's state
+        $c->logout;
+    
+        # Send the user to the starting point
+        $c->response->redirect($c->uri_for('/'));
+    }
+
+As with the login controller, be sure to delete the 
+C<$c->response->body('Matched MyApp::Controller::Logout in Logout.');>
+line of the C<sub index>.
+
+
+=head2 Add a Login Form TT Template Page
+
+Create a login form by opening C<root/src/login.tt2> and inserting:
+
+    [% META title = 'Login' %]
+    
+    <!-- Login form -->
+    <form method="post" action=" [% Catalyst.uri_for('/login') %] ">
+      <table>
+        <tr>
+          <td>Username:</td>
+          <td><input type="text" name="username" size="40" /></td>
+        </tr>
+        <tr>
+          <td>Password:</td>
+          <td><input type="password" name="password" size="40" /></td>
+        </tr>
+        <tr>
+          <td colspan="2"><input type="submit" name="submit" value="Submit" /></td>
+        </tr>
+      </table>
+    </form>
+
+
+=head2 Add Valid User Check
+
+We need something that provides enforcement for the authentication
+mechanism -- a I<global> mechanism that prevents users who have not
+passed authentication from reaching any pages except the login page.
+This is generally done via an C<auto> action/method (prior to Catalyst
+v5.66, this sort of thing would go in C<MyApp.pm>, but starting in
+v5.66, the preferred location is C<lib/MyApp/Controller/Root.pm>).
+
+Edit the existing C<lib/MyApp/Controller/Root.pm> class file and insert
+the following method:
+
+    =head2 auto
+    
+    Check if there is a user and, if not, forward to login page
+    
+    =cut
+    
+    # Note that 'auto' runs after 'begin' but before your actions and that
+    # 'auto' "chain" (all from application path to most specific class are run)
+    # See the 'Actions' section of 'Catalyst::Manual::Intro' for more info.
+    sub auto : Private {
+        my ($self, $c) = @_;
+    
+        # Allow unauthenticated users to reach the login page.  This
+        # allows anauthenticated users to reach any action in the Login
+        # controller.  To lock it down to a single action, we could use:
+        #   if ($c->action eq $c->controller('Login')->action_for('index'))
+        # to only allow unauthenticated access to the C<index> action we
+        # added above.
+        if ($c->controller eq $c->controller('Login')) {
+            return 1;
+        }
+    
+        # If a user doesn't exist, force login
+        if (!$c->user_exists) {
+            # Dump a log message to the development server debug output
+            $c->log->debug('***Root::auto User not found, forwarding to /login');
+            # Redirect the user to the login page
+            $c->response->redirect($c->uri_for('/login'));
+            # Return 0 to cancel 'post-auto' processing and prevent use of application
+            return 0;
+        }
+    
+        # User found, so return 1 to continue with processing after this 'auto'
+        return 1;
+    }
+
+B<Note:> Catalyst provides a number of different types of actions, such
+as C<Local>, C<Regex>, and C<Private>.  You should refer to
+L<Catalyst::Manual::Intro> for a more detailed explanation, but the
+following bullet points provide a quick introduction:
+
+=over 4
+
+=item *
+
+The majority of application use C<Local> actions for items that respond
+to user requests and C<Private> actions for those that do not directly
+respond to user input.
+
+=item *
+
+There are five types of C<Private> actions: C<begin>, C<end>,
+C<default>, C<index>, and C<auto>.
+
+=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
+
+By placing the authentication enforcement code inside the C<auto> method
+of C<lib/MyApp/Controller/Root.pm> (or C<lib/MyApp.pm>), it will be
+called for I<every> request that is received by the entire application.
+
+
+=head2 Displaying Content Only to Authenticated Users
+
+Let's say you want to provide some information on the login page that
+changes depending on whether the user has authenticated yet.  To do
+this, open C<root/src/login.tt2> in your editor and add the following
+lines to the bottom of the file:
+
+    <p>
+    [%
+       # This code illustrates how certain parts of the TT 
+       # template will only be shown to users who have logged in
+    %]
+    [% IF Catalyst.user_exists %]
+        Please Note: You are already logged in as '[% Catalyst.user.username %]'.
+        You can <a href="[% Catalyst.uri_for('/logout') %]">logout</a> here.
+    [% ELSE %]
+        You need to log in to use this application.
+    [% END %]
+    [%#
+       Note that this whole block is a comment because the "#" appears
+       immediate after the "[%" (with no spaces in between).  Although it 
+       can be a handy way to temporarily "comment out" a whole block of 
+       TT code, it's probably a little too subtle for use in "normal" 
+       comments.
+    %]
+
+Although most of the code is comments, the middle few lines provide a
+"you are already logged in" reminder if the user returns to the login
+page after they have already authenticated.  For users who have not yet
+authenticated, a "You need to log in..." message is displayed (note the
+use of an IF-THEN-ELSE construct in TT).
+
+
+=head2 Try Out Authentication
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still
+running) and restart it:
+
+    $ script/myapp_server.pl
+
+B<IMPORTANT NOTE>: If you happen to be using Internet Explorer, you may
+need to use the command C<script/myapp_server.pl -k> to enable the
+keepalive feature in the development server.  Otherwise, the HTTP
+redirect on successful login may not work correctly with IE (it seems to
+work without -k if you are running the web browser and development
+server on the same machine).  If you are using browser a browser other
+than IE, it should work either way.  If you want to make keepalive the
+default, you can edit C<script/myapp_server.pl> and change the
+initialization value for C<$keepalive> to C<1>.  (You will need to do
+this every time you create a new Catalyst application or rebuild the
+C<myapp_server.pl> script.)
+
+Now trying going to L<http://localhost:3000/books/list> and you should
+be redirected to the login page, hitting Shift+Reload if necessary (the
+"You are already logged in" message should I<not> appear -- if it does,
+click the C<logout> button and try again). Note the C<***Root::auto User
+not found...> debug message in the development server output.  Enter
+username C<test01> and password C<mypass>, and you should be taken to
+the Book List page.
+
+Open C<root/src/books/list.tt2> and add the following lines to the
+bottom:
+
+    <p>
+      <a href="[% Catalyst.uri_for('/login') %]">Login</a>
+      <a href="[% Catalyst.uri_for('form_create') %]">Create</a>
+    </p>
+
+Reload your browser and you should now see a "Login" and "Create" links 
+at the bottom of the page (as mentioned earlier, you can update template 
+files without reloading the development server).  Click the first link 
+to return to the login page.  This time you I<should> see the "You are 
+already logged in" message.
+
+Finally, click the C<You can logout here> link on the C</login> page.
+You should stay at the login page, but the message should change to "You
+need to log in to use this application."
+
+
+=head1 USING PASSWORD HASHES
+
+In this section we increase the security of our system by converting
+from cleartext passwords to SHA-1 password hashes.
+
+B<Note:> This section is optional.  You can skip it and the rest of the
+tutorial will function normally.
+
+Note that even with the techniques shown in this section, the browser
+still transmits the passwords in cleartext to your application.  We are
+just avoiding the I<storage> of cleartext passwords in the database by
+using a SHA-1 hash. If you are concerned about cleartext passwords
+between the browser and your application, consider using SSL/TLS, made
+easy with the Catalyst plugin Catalyst::Plugin:RequireSSL.
+
+
+=head2 Get a SHA-1 Hash for the Password
+
+Catalyst uses the C<Digest> module to support a variety of hashing
+algorithms.  Here we will use SHA-1 (SHA = Secure Hash Algorithm).
+First, we should compute the SHA-1 hash for the "mypass" password we are
+using.  The following command-line Perl script provides a "quick and
+dirty" way to do this:
+
+    $ perl -MDigest::SHA -e 'print Digest::SHA::sha1_hex("mypass"), "\n"'
+    e727d1464ae12436e899a726da5b2f11d8381b26
+    $
+
+B<Note:> You should probably modify this code for production use to
+not read the password from the command line.  By having the script
+prompt for the cleartext password, it avoids having the password linger
+in forms such as your C<.bash_history> files (assuming you are using
+BASH as your shell).  An example of such a script can be found in
+Appendix 3.
+
+
+=head2 Switch to SHA-1 Password Hashes in the Database
+
+Next, we need to change the C<password> column of our C<users> table to
+store this hash value vs. the existing cleartext password.  Open
+C<myapp03.sql> in your editor and enter:
+
+    --
+    -- Convert passwords to SHA-1 hashes
+    --
+    UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 1;
+    UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 2;
+    UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 3;
+
+Then use the following command to update the SQLite database:
+
+    $ sqlite3 myapp.db < myapp03.sql
+
+B<Note:> We are using SHA-1 hashes here, but many other hashing
+algorithms are supported.  See C<Digest> for more information.
+
+
+=head2 Enable SHA-1 Hash Passwords in
+C<Catalyst::Plugin::Authentication::Store::DBIC>
+
+Edit C<myapp.yml> and update it to match (the C<password_type> and
+C<password_hash_type> are new, everything else is the same):
+
+    ---
+    name: MyApp
+    authentication:
+        dbic:
+            # Note this first definition would be the same as setting
+            # __PACKAGE__->config->{authentication}->{dbic}->{user_class} = 'MyAppDB::User'
+            # in lib/MyApp.pm (IOW, each hash key becomes a "name:" in the YAML file).
+            #
+            # This is the model object created by Catalyst::Model::DBIC from your
+            # schema (you created 'MyAppDB::User' but as the Catalyst startup
+            # debug messages show, it was loaded as 'MyApp::Model::MyAppDB::User').
+            # NOTE: Omit 'MyApp::Model' here just as you would when using 
+            # '$c->model("MyAppDB::User)'
+            user_class: MyAppDB::User
+            # This is the name of the field in your 'users' table that contains the user's name
+            user_field: username
+            # This is the name of the field in your 'users' table that contains the password
+            password_field: password
+            # Other options can go here for hashed passwords
+            # Enabled hashed passwords
+            password_type: hashed
+            # Use the SHA-1 hashing algorithm
+            password_hash_type: SHA-1
+
+
+=head2 Try Out the Hashed Passwords
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still
+running) and restart it:
+
+    $ script/myapp_server.pl
+
+You should now be able to go to L<http://localhost:3000/books/list> and
+login as before.  When done, click the "Logout" link on the login page
+(or point your browser at L<http://localhost:3000/logout>).
+
+B<Note:> If you receive the debug screen in your browser with a 
+C<Can't call method "stash" on an undefined value...> error message,
+make sure that you are using v0.07 of 
+L<Catalyst::Plugin::Authorization::ACL|Catalyst::Plugin::Authorization::ACL>.
+The following command can be a useful way to quickly dump the version number
+of this module on your system:
+
+    perl -MCatalyst::Plugin::Authorization::ACL -e 'print $Catalyst::Plugin::Authorization::ACL::VERSION, "\n";'
+
+
+=head1 USING THE SESSION FOR FLASH
+
+As discussed in Part 3 of the tutorial, C<flash> allows you to set
+variables in a way that is very similar to C<stash>, but it will 
+remain set across multiple requests.  Once the value is read, it
+is cleared (unless reset).  Although C<flash> has nothing to do with
+authentication, it does leverage the same session plugins.  Now that
+those plugins are enabled, let's go back and improve the "delete
+and redirect with query parameters" code seen at the end of the
+L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD> part of the 
+tutorial.
+
+First, open C<lib/MyApp/Controller/Books.pm> and modify C<sub delete>
+to match the following:
+
+    =head2 delete 
+    
+    Delete a book
+        
+    =cut
+    
+    sub delete : Local {
+        # $id = primary key of book to delete
+        my ($self, $c, $id) = @_;
+    
+        # Search for the book and then delete it
+        $c->model('MyAppDB::Book')->search({id => $id})->delete_all;
+    
+        # Use 'flash' to save information across requests until it's read
+        $c->flash->{status_msg} = "Book deleted";
+            
+        # Redirect the user back to the list page with status msg as an arg
+        $c->response->redirect($c->uri_for('/books/list'));
+    }
+
+Next, open C<root/lib/site/layout> and update the TT code to pull from 
+flash vs. the C<status_msg> query parameter:
+
+    <div id="header">[% PROCESS site/header %]</div>
+    
+    <div id="content">
+    <span class="message">[% status_msg || Catalyst.flash.status_msg %]</span>
+    <span class="error">[% error_msg %]</span>
+    [% content %]
+    </div>
+    
+    <div id="footer">[% PROCESS site/footer %]</div>
+
+
+=head2 Try Out Flash
+
+Restart the development server and point your browser to 
+L<http://localhost:3000/books/url_create/Test/1/4> to create an extra
+book.  Click the "Return to list" link and delete the "Test" book you
+just added.  The C<flash> mechanism should retain our "Book deleted" 
+status message across the redirect.
+
+B<NOTE:> While C<flash> will save information across multiple requests,
+I<it does get cleared the first time it is read>.  In general, this is
+exactly what you want -- the C<flash> message will get displayed on
+the next screen where it's appropriate, but it won't "keep showing up"
+after that first time (unless you reset it).  Please refer to
+L<Catalyst::Plugin::Session|Catalyst::Plugin::Session> for additional
+information.
+
+
+=head1 AUTHOR
+
+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-Runtime/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
diff --git a/lib/Catalyst/Manual/Tutorial/Tutorial/Authorization.pod b/lib/Catalyst/Manual/Tutorial/Tutorial/Authorization.pod
new file mode 100644 (file)
index 0000000..9af1ce3
--- /dev/null
@@ -0,0 +1,413 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::Authorization - Catalyst Tutorial - Part 5: Authorization
+
+
+=head1 OVERVIEW
+
+This is B<Part 5 of 9> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
+
+=item 3
+
+L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>
+
+=item 4
+
+L<Authentication|Catalyst::Manual::Tutorial::Authentication>
+
+=item 5
+
+B<Authorization>
+
+=item 6
+
+L<Debugging|Catalyst::Manual::Tutorial::Debugging>
+
+=item 7
+
+L<Testing|Catalyst::Manual::Tutorial::Testing>
+
+=item 8
+
+L<AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
+
+=item 9
+
+L<Appendices|Catalyst::Manual::Tutorial::Appendices>
+
+=back
+
+
+
+=head1 DESCRIPTION
+
+This part of the tutorial adds role-based authorization to the existing
+authentication implemented in Part 4.  It provides simple examples of
+how to use roles in both TT templates and controller actions.  The first
+half looks at manually configured authorization.  The second half looks
+at how the ACL authorization plugin can simplify your code.
+
+You can checkout the source code for this example from the catalyst
+subversion repository as per the instructions in
+L<Catalyst::Manual::Tutorial::Intro>
+
+=head1 BASIC AUTHORIZATION
+
+In this section you learn how to manually configure authorization.
+
+=head2 Update Plugins to Include Support for Authorization
+
+Edit C<lib/MyApp.pm> and add C<Authorization::Roles> to the list:
+
+    use Catalyst qw/
+            -Debug
+            ConfigLoader
+            Static::Simple
+            
+            StackTrace
+            
+            Authentication
+            Authentication::Store::DBIC
+            Authentication::Credential::Password
+            Authorization::Roles
+            
+            Session
+            Session::Store::FastMmap
+            Session::State::Cookie
+            /;
+
+
+=head2 Add Config Information for Authorization
+
+Edit C<myapp.yml> and update it to match (everything from the
+"authorization:" line down is new):
+
+    ---
+    name: MyApp
+    authentication:
+        dbic:
+            # Note this first definition would be the same as setting
+            # __PACKAGE__->config->{authentication}->{dbic}->{user_class} = 'MyAppDB::User'
+            # in lib/MyApp.pm (IOW, each hash key becomes a "name:" in the YAML file).
+            #
+            # This is the model object created by Catalyst::Model::DBIC from your
+            # schema (you created 'MyAppDB::User' but as the Catalyst startup
+            # debug messages show, it was loaded as 'MyApp::Model::MyAppDB::User').
+            # NOTE: Omit 'MyApp::Model' here just as you would when using 
+            # '$c->model("MyAppDB::User)'
+            user_class: MyAppDB::User
+            # This is the name of the field in your 'users' table that contains the user's name
+            user_field: username
+            # This is the name of the field in your 'users' table that contains the password
+            password_field: password
+            # Other options can go here for hashed passwords
+            # Enabled hashed passwords
+            password_type: hashed
+            # Use the SHA-1 hashing algorithm
+            password_hash_type: SHA-1
+    authorization:
+        dbic:
+            # This is the model object created by Catalyst::Model::DBIC from your
+            # schema (you created 'MyAppDB::Role' but as the Catalyst startup
+            # debug messages show, it was loaded as 'MyApp::Model::MyAppDB::Role').
+            # NOTE: Omit 'MyApp::Model' here just as you would when using 
+            # '$c->model("MyAppDB::User)'
+            role_class: MyAppDB::Role
+            # The name of the field in the 'roles' table that contains the role name
+            role_field: role
+            # The name of the accessor used to map a role to the users who have this role
+            # See the has_many() in MyAppDB/Role.pm
+            role_rel: map_user_role
+            # The name of the field in the user_role table that references the user
+            user_role_user_field: user_id
+
+
+=head2 Add Role-Specific Logic to the "Book List" Template
+
+Open C<root/src/books/list.tt2> in your editor and add the following
+lines to the bottom of the file:
+
+    <p>Hello [% Catalyst.user.username %], you have the following roles:</p>
+    
+    <ul>
+      [% # Dump list of roles -%]
+      [% FOR role = Catalyst.user.roles %]<li>[% role %]</li>[% END %]
+    </ul>
+    
+    <p>
+    [% # Add some simple role-specific logic to template %]
+    [% # Use $c->check_user_roles() to check authz -%]
+    [% IF Catalyst.check_user_roles('user') %]
+      [% # Give normal users a link for 'logout' %]
+      <a href="[% Catalyst.uri_for('/logout') %]">Logout</a>
+    [% END %]
+    
+    [% # Can also use $c->user->check_roles() to check authz -%]
+    [% IF Catalyst.check_user_roles('admin') %]
+      [% # Give admin users a link for 'create' %]
+      <a href="[% Catalyst.uri_for('form_create') %]">Create</a>
+    [% END %]
+    </p>
+
+This code displays a different combination of links depending on the
+roles assigned to the user.
+
+=head2 Limit C<Books::add> to C<admin> Users
+
+C<IF> statements in TT templates simply control the output that is sent
+to the user's browser; it provides no real enforcement (if users know or
+guess the appropriate URLs, they are still perfectly free to hit any
+action within your application).  We need to enhance the controller
+logic to wrap restricted actions with role-validation logic.
+
+For example, we might want to restrict the "formless create" action to
+admin-level users by editing C<lib/MyApp/Controller/Books.pm> and
+updating C<url_create> to match the following code:
+
+    =head2 url_create
+
+    Create a book with the supplied title and rating,
+    with manual authorization
+    
+    =cut
+    
+    sub url_create : Local {
+        # In addition to self & context, get the title, rating & author_id args
+        # from the URL.  Note that Catalyst automatically puts extra information
+        # after the "/<controller_name>/<action_name/" into @_
+        my ($self, $c, $title, $rating, $author_id) = @_;
+    
+        # Check the user's roles
+        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('MyAppDB::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});
+            # Note: Above is a shortcut for this:
+            # $book->create_related('book_authors', {author_id => $author_id});
+            
+            # Assign the Book object to the stash for display in the view
+            $c->stash->{book} = $book;
+        
+            # This is a hack to disable XSUB processing in Data::Dumper
+            # (it's used in the view).  This is a work-around for a bug in
+            # the interaction of some versions or Perl, Data::Dumper & DBIC.
+            # You won't need this if you aren't using Data::Dumper (or if
+            # you are running DBIC 0.06001 or greater), but adding it doesn't 
+            # hurt anything either.
+            $Data::Dumper::Useperl = 1;
+        
+            # Set the TT template to use
+            $c->stash->{template} = 'books/create_done.tt2';
+        } else {
+            # Provide very simple feedback to the user
+            $c->response->body('Unauthorized!');
+        }
+    }
+
+
+To add authorization, we simply wrap the main code of this method in an
+C<if> statement that calls C<check_user_roles>.  If the user does not
+have the appropriate permissions, they receive an "Unauthorized!"
+message.  Note that we intentionally chose to display the message this
+way to demonstrate that TT templates will not be used if the response
+body has already been set.  In reality you would probably want to use a
+technique that maintains the visual continuity of your template layout
+(for example, using the "status" or "error" message feature added in
+Part 2).
+
+B<TIP>: If you want to keep your existing C<url_create> method, you can
+create a new copy and comment out the original by making it look like a
+Pod comment.  For example, put something like C<=begin> before C<sub add
+: Local {> and C<=end> after the closing C<}>.
+
+=head2 Try Out Authentication And Authorization
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still
+running) and restart it:
+
+    $ script/myapp_server.pl
+
+Now trying going to L<http://localhost:3000/books/list> and you should
+be taken to the login page (you might have to C<Shift+Reload> your
+browser and/or click the "Logout" link on the book list page).  Try 
+logging in with both C<test01> and C<test02> (both use a password 
+of C<mypass>) and notice how the roles information updates at the 
+bottom of the "Book List" page. Also try the C<Logout> link on the
+book list page.
+
+Now the "url_create" URL will work if you are already logged in as user
+C<test01>, but receive an authorization failure if you are logged in as
+C<test02>.  Try:
+
+    http://localhost:3000/books/url_create/test/1/6
+
+while logged in as each user.  Use one of the 'Logout' links (or go to
+L<http://localhost:3000/logout> in you browser directly) when you are
+done.
+
+
+=head1 ENABLE ACL-BASED AUTHORIZATION
+
+This section takes a brief look at how the
+L<Catalyst::Plugin::Authorization::ACL|Catalyst::Plugin::Authorization::ACL>
+plugin can automate much of the work required to perform role-based 
+authorization in a Catalyst application.
+
+=head2 Add the C<Catalyst::Plugin::Authorization::ACL> Plugin
+
+Open C<lib/MyApp.pm> in your editor and add the following plugin to the
+C<use Catalyst> statement:
+
+    Authorization::ACL
+
+Note that the remaining C<use Catalyst> plugins from earlier sections
+are not shown here, but they should still be included.
+
+=head2 Add ACL Rules to the Application Class
+
+Open C<lib/MyApp.pm> in your editor and add the following B<BELOW> the
+C<__PACKAGE__-E<gt>setup;> statement:
+
+    # Authorization::ACL Rules
+    __PACKAGE__->deny_access_unless(
+            "/books/form_create",
+            [qw/admin/],
+        );
+    __PACKAGE__->deny_access_unless(
+            "/books/form_create_do",
+            [qw/admin/],
+        );
+    __PACKAGE__->deny_access_unless(
+            "/books/delete",
+            [qw/user admin/],
+        );
+
+Each of the three statements above comprises an ACL plugin "rule".  The
+first two rules only allow admin-level users to create new books using
+the form (both the form itself and the data submission logic are
+protected).  The third statement allows both users and admins to delete
+books.  The C</books/url_create> action will continue to be protected by
+the "manually configured" authorization created earlier in this part of
+the tutorial.
+
+The ACL plugin permits you to apply allow/deny logic in a variety of
+ways.  The following provides a basic overview of the capabilities:
+
+=over 4
+
+=item * 
+
+The ACL plugin only operates on the Catalyst "private namespace".  You
+are using the private namespace when you use C<Local> actions.  C<Path>,
+C<Regex>, and C<Global> allow you to specify actions where the path and
+the namespace differ -- the ACL plugin will not work in these cases.
+
+=item * 
+
+Each rule is expressed in a separate
+C<__PACKAGE__-E<gt>deny_access_unless()> or
+C<__PACKAGE__-E<gt>allow_access_if()> line (there are several other
+methods that can be used for more complex policies, see the C<METHODS>
+portion of the
+L<Catalyst::Plugin::Authorization::ACL|Catalyst::Plugin::Authorization::ACL>
+documentation for more details).
+
+=item * 
+
+Each rule can contain multiple roles but only a single path.
+
+=item * 
+
+The rules are tried in order (with the "most specific" rules tested
+first), and processing stops at the first "match" where an allow or deny
+is specified.  Rules "fall through" if there is not a "match" (where a
+"match" means the user has the specified role).  If a "match" is found,
+then processing stops there and the appropriate allow/deny action is
+taken.
+
+=item * 
+
+If none of the rules match, then access is allowed.
+
+=item * 
+
+The rules currently need to be specific in the application class
+C<lib\MyApp.pm> B<after> the C<__PACKAGE__-E<gt>setup;> line.
+
+=back
+
+=head2 Add a Method to Handle Access Violations
+
+By default,
+L<Catalyst::Plugin::Authorization::ACL|Catalyst::Plugin::Authorization::ACL>
+throws an exception when authorization fails.  This will take the user
+to the Catalyst debug screen, or a "Please come back later" message if
+you are not using the C<-Debug> flag. This step uses the
+C<access_denied> method in order to provide more appropriate feedback to
+the user.
+
+Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
+following method:
+
+    =head2 access_denied
+    
+    Handle Catalyst::Plugin::Authorization::ACL access denied exceptions
+    
+    =cut
+    
+    sub access_denied : Private {
+        my ($self, $c) = @_;
+    
+        # Set the error message
+        $c->stash->{error_msg} = 'Unauthorized!';
+    
+        # Display the list
+        $c->forward('list');
+    }
+
+Then run the Catalyst development server script:    
+
+    $ script/myapp_server.pl
+
+Log in as C<test02>.  Once at the book list, click the "Create" link to
+try the C<form_create> action.  You should receive a red "Unauthorized!"
+error message at the top of the list.  (Note that in reality you would
+probably want to place the "Create" link code in
+C<root/src/books/list.tt2> inside an C<IF> statement that only displays
+the list to admin-level users.)  If you log in as C<test01> you should
+be able to view the C<form_create> form and add a new book.
+
+When you are done, use one of the 'Logout' links (or go to the
+L<http://localhost:3000/logout> URL directly) when you are done.
+
+
+=head1 AUTHOR
+
+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-Runtime/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
+
diff --git a/lib/Catalyst/Manual/Tutorial/Tutorial/BasicCRUD.pod b/lib/Catalyst/Manual/Tutorial/Tutorial/BasicCRUD.pod
new file mode 100644 (file)
index 0000000..8c524c7
--- /dev/null
@@ -0,0 +1,568 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::BasicCRUD - Catalyst Tutorial - Part 3: Basic CRUD
+
+
+=head1 OVERVIEW
+
+This is B<Part 3 of 9> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
+
+=item 3
+
+B<Basic CRUD>
+
+=item 4
+
+L<Authentication|Catalyst::Manual::Tutorial::Authentication>
+
+=item 5
+
+L<Authorization|Catalyst::Manual::Tutorial::Authorization>
+
+=item 6
+
+L<Debugging|Catalyst::Manual::Tutorial::Debugging>
+
+=item 7
+
+L<Testing|Catalyst::Manual::Tutorial::Testing>
+
+=item 8
+
+L<AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
+
+=item 9
+
+L<Appendices|Catalyst::Manual::Tutorial::Appendices>
+
+=back
+
+
+
+=head1 DESCRIPTION
+
+This part of the tutorial builds on the fairly primitive application
+created in Part 2 to add basic support for Create, Read, Update, and
+Delete (CRUD) of C<Book> objects.  Note that the 'list' function in Part
+2 already implements the Read portion of CRUD (although Read normally
+refers to reading a single object; you could implement full read
+functionality using the techniques introduced below).  This section will
+focus on the Create and Delete aspects of CRUD.  More advanced
+capabilities, including full Update functionality, will be addressed in
+Part 8.
+
+You can checkout the source code for this example from the catalyst
+subversion repository as per the instructions in
+L<Catalyst::Manual::Tutorial::Intro>
+
+=head1 FORMLESS SUBMISSION
+
+Our initial attempt at object creation will utilize the "URL arguments"
+feature of Catalyst (we will employ the more common form-based
+submission in the sections that follow).
+
+
+=head2 Include a Create Action in the Books Controller
+
+Edit C<lib/MyApp/Controller/Books.pm> and enter the following method:
+
+    =head2 url_create
+    
+    Create a book with the supplied title, rating, and author
+    
+    =cut
+    
+    sub url_create : Local {
+        # In addition to self & context, get the title, rating, & 
+        # author_id args from the URL.  Note that Catalyst automatically 
+        # puts extra information after the "/<controller_name>/<action_name/" 
+        # into @_
+        my ($self, $c, $title, $rating, $author_id) = @_;
+    
+        # Call create() on the book model object. Pass the table 
+        # columns/field values we want to set as hash values
+        my $book = $c->model('MyAppDB::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});
+        # Note: Above is a shortcut for this:
+        # $book->create_related('book_authors', {author_id => $author_id});
+        
+        # Assign the Book object to the stash for display in the view
+        $c->stash->{book} = $book;
+    
+        # This is a hack to disable XSUB processing in Data::Dumper
+        # (it's used in the view).  This is a work-around for a bug in
+        # the interaction of some versions or Perl, Data::Dumper & DBIC.
+        # You won't need this if you aren't using Data::Dumper (or if
+        # you are running DBIC 0.06001 or greater), but adding it doesn't 
+        # hurt anything either.
+        $Data::Dumper::Useperl = 1;
+    
+        # Set the TT template to use
+        $c->stash->{template} = 'books/create_done.tt2';
+    }
+
+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
+controller methods (at least the ones that directly handle user input),
+it then sets the template that should handle this request.
+
+
+=head2 Include a Template for the C<url_create> Action:
+
+Edit C<root/src/books/create_done.tt2> and then enter:
+
+    [% # Use the TT Dumper plugin to Data::Dumper variables to the browser   -%]
+    [% # Not a good idea for production use, though. :-)  'Indent=1' is      -%]
+    [% # optional, but prevents "massive indenting" of deeply nested objects -%]
+    [% USE Dumper(Indent=1) -%]
+    
+    [% # Set the page title.  META can 'go back' and set values in templates -%]
+    [% # that have been processed 'before' this template (here it's for      -%]
+    [% # root/lib/site/html and root/lib/site/header).  Note that META on    -%]
+    [% # simple strings (e.g., no variable interpolation).                   -%]
+    [% META title = 'Book Created' %]
+    
+    [% # Output information about the record that was added.  First title.       -%]
+    <p>Added book '[% book.title %]'
+    
+    [% # 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 %]
+    [% # 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; 
+           authors.list.first.value.last_name IF ! authors.first %]'
+    
+    [% # Output the rating for the book that was added -%]
+    with a rating of [% book.rating %].</p>
+    
+    [% # Provide a link back to the list page                                    -%]
+    [% # 'uri_for()' builds a full URI; e.g., 'http://localhost:3000/books/list' -%]
+    <p><a href="[% Catalyst.uri_for('/books/list') %]">Return to list</a></p>
+    
+    [% # Try out the TT Dumper (for development only!) -%]
+    <pre>
+    Dump of the 'book' variable:
+    [% Dumper.dump(book) %]
+    </pre>
+
+The TT C<USE> directive allows access to a variety of plugin modules (TT
+plugins, that is, not Catalyst plugins) to add extra functionality to
+the base TT capabilities.  Here, the plugin allows L<Data::Dumper>
+"pretty printing" of objects and variables.  Other than that, the rest
+of the code should be familiar from the examples in Part 2.
+
+B<IMPORTANT NOTE> As mentioned earlier, the C<MyApp::View::TT.pm> view
+class created by TTSite redefines the name used to access the Catalyst
+context object in TT templates from the usual C<c> to C<Catalyst>.
+
+=head2 Try the C<url_create> Feature
+
+If the application is still running from before, use C<Ctrl-C> to kill
+it. Then restart the server:
+
+    $ script/myapp_server.pl
+
+Note that new path for C</books/url_create> appears in the startup debug
+output.
+
+B<TIP>: You can use C<script/myapp_server.pl -r> to have the development
+server auto-detect changed files and reload itself (if your browser acts
+odd, you should also try throwing in a C<-k>).  If you make changes to
+the TT templates only, you do not need to reload the development server
+(only changes to "compiled code" such as Controller and Model C<.pm>
+files require a reload).
+
+Next, use your browser to enter the following URL:
+
+    http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4
+
+Your browser should display " Added book 'TCPIP_Illustrated_Vol-2' by
+'Stevens' with a rating of 5." along with a dump of the new book model
+object.  You should also see the following DBIC debug messages displayed
+in the development server log messages:
+
+    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'
+
+The C<INSERT> statements are obviously adding the book and linking it to
+the existing record for Richard Stevens.  The C<SELECT> statement results
+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 your browser at the
+C</books/list> page).
+
+Then I<add 2 more copies of the same book> so that we have some extras for
+our delete logic that will be coming up soon.  Enter the same URL above
+two more times (or refresh your browser twice if it still contains this
+URL):
+
+    http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4
+
+You should be able to click "Return to list" and now see 3 copies of 
+"TCP_Illustrated_Vol-2".
+
+
+=head1 MANUALLY BUILDING A CREATE FORM
+
+Although the C<url_create> action in the previous step does begin to
+reveal the power and flexibility of both Catalyst and DBIC, it's
+obviously not a very realistic example of how users should be expected
+to enter data.  This section begins to address that concern.
+
+
+=head2 Add Method to Display The Form
+
+Edit C<lib/MyApp/Controller/Books.pm> and add the following method:
+
+    =head2 form_create
+    
+    Display form to collect information for book to create
+    
+    =cut
+    
+    sub form_create : Local {
+        my ($self, $c) = @_;
+    
+        # Set the TT template to use
+        $c->stash->{template} = 'books/form_create.tt2';
+    }
+
+This action simply invokes a view containing a book creation form.
+
+=head2 Add a Template for the Form
+
+Open C<root/src/books/form_create.tt2> in your editor and enter:
+
+    [% META title = 'Manual Form Book Create' -%]
+    
+    <form method="post" action="[% Catalyst.uri_for('form_create_do') %]">
+    <table>
+      <tr><td>Title:</td><td><input type="text" name="title"></td></tr>
+      <tr><td>Rating:</td><td><input type="text" name="rating"></td></tr>
+      <tr><td>Author ID:</td><td><input type="text" name="author_id"></td></tr>
+    </table>
+    <input type="submit" name="Submit" value="Submit">
+    </form>
+
+Note that we have specified the target of the form data as
+C<form_create_do>, the method created in the section that follows.
+
+=head2 Add a Method to Process Form Values and Update Database
+
+Edit C<lib/MyApp/Controller/Books.pm> and add the following method to
+save the form information to the database:
+
+    =head2 form_create_do
+    
+    Take information from form and add to database
+    
+    =cut
+    
+    sub form_create_do : Local {
+        my ($self, $c) = @_;
+    
+        # Retrieve the values from the form
+        my $title     = $c->request->params->{title}     || 'N/A';
+        my $rating    = $c->request->params->{rating}    || 'N/A';
+        my $author_id = $c->request->params->{author_id} || '1';
+    
+        # Create the book
+        my $book = $c->model('MyAppDB::Book')->create({
+                title   => $title,
+                rating  => $rating,
+            });
+        # Handle relationship with author
+        $book->add_to_book_authors({author_id => $author_id});
+    
+        # Store new model object in stash
+        $c->stash->{book} = $book;
+    
+        # Avoid Data::Dumper issue mentioned earlier
+        # You can probably omit this    
+        $Data::Dumper::Useperl = 1;
+    
+        # Set the TT template to use
+        $c->stash->{template} = 'books/create_done.tt2';
+    }
+
+
+=head2 Test Out The Form
+
+If the application is still running from before, use C<Ctrl-C> to kill
+it.  Then restart the server:
+
+    $ script/myapp_server.pl
+
+Point your browser to L<http://localhost:3000/books/form_create> and
+enter "TCP/IP Illustrated, Vol 3" for the title, a rating of 5, and an
+author ID of 4.  You should then be forwarded to the same
+C<create_done.tt2> template seen in earlier examples.  Finally, click
+"Return to list" to view the full list of books.
+
+B<Note:> Having the user enter the primary key ID for the author is
+obviously crude; we will address this concern with a drop-down list in
+Part 8.
+
+
+=head1 A SIMPLE DELETE FEATURE
+
+Turning our attention to the delete portion of CRUD, this section
+illustrates some basic techniques that can be used to remove information
+from the database.
+
+
+=head2 Include a Delete Link in the List
+
+Edit C<root/src/books/list.tt2> and update it to the following (two
+sections have changed: 1) the additional '<th>Links</th>' table header,
+and 2) the four lines for the Delete link near the bottom).
+
+    [% # This is a TT comment.  The '-' at the end "chomps" the newline.  You won't -%]
+    [% # see this "chomping" in your browser because HTML ignores blank lines, but  -%]
+    [% # it WILL eliminate a blank line if you view the HTML source.  It's purely   -%]
+    [%- # optional, but both the beginning and the ending TT tags support chomping. -%]
+    
+    [% # Provide a title to root/lib/site/header -%]
+    [% META title = 'Book List' -%]
+    
+    <table>
+    <tr><th>Title</th><th>Rating</th><th>Author(s)</th><th>Links</th></tr>
+    [% # Display each book in a table row %]
+    [% FOREACH book IN books -%]
+      <tr>
+        <td>[% book.title %]</td>
+        <td>[% book.rating %]</td>
+        <td>
+          [% # First initialize a TT variable to hold a list.  Then use a TT FOREACH -%]
+          [% # loop in 'side effect notation' to load just the last names of the     -%]
+          [% # authors into the list.  Note that the 'push' TT vmethod does not      -%]
+          [% # a value, so nothing will be printed here.  But, if you have something -%]
+          [% # in TT that does return a method 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.                                 -%]
+          [% tt_authors = [ ];
+             tt_authors.push(author.last_name) FOREACH author = book.authors %]
+          [% # Now use a TT 'virtual method' to display the author count in parens   -%]
+          ([% tt_authors.size %])
+          [% # Use another TT vmethod to join & print the names & comma separators   -%]
+          [% tt_authors.join(', ') %]
+        </td>
+        <td>
+          [% # Add a link to delete a book %]
+          <a href="[% Catalyst.uri_for('delete/') _ book.id %]">Delete</a>
+        </td>
+      </tr>
+    [% END -%]
+    </table>
+
+The additional code is obviously designed to add a new column to the
+right side of the table with a C<Delete> "button" (for simplicity, links
+will be used instead of full HTML buttons).
+
+=head2 Add a Delete Action to the Controller
+
+Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
+following method:
+
+    =head2 delete 
+    
+    Delete a book
+        
+    =cut
+    
+    sub delete : Local {
+        # $id = primary key of book to delete
+        my ($self, $c, $id) = @_;
+    
+        # Search for the book and then delete it
+        $c->model('MyAppDB::Book')->search({id => $id})->delete_all;
+    
+        # Set a status message to be displayed at the top of the view
+        $c->stash->{status_msg} = "Book deleted.";
+    
+        # Forward to the list action/method in this controller
+        $c->forward('list');
+    }
+
+This method first deletes the book with the specified primary key ID.
+However, it also removes the corresponding entry from the
+C<book_authors> table.  Note that C<delete_all> was used instead of
+C<delete>: whereas C<delete_all> also removes the join table entries in
+C<book_authors>, C<delete> does not (only use C<delete_all> if you
+really need the cascading deletes... otherwise you are wasting resources).
+
+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
+notification to the user as the normal list view is rendered.
+
+The C<delete> action uses the context C<forward> method to return the
+user to the book list.  The C<detach> method could have also been used.
+Whereas C<forward> I<returns> to the original action once it is
+completed, C<detach> does I<not> return.  Other than that, the two are
+equivalent.
+
+
+=head2 Try the Delete Feature
+
+If the application is still running from before, use C<Ctrl-C> to kill
+it.  Then restart the server:
+
+    $ script/myapp_server.pl
+
+Then point your browser to L<http://localhost:3000/books/list> and click
+the "Delete" link next to the first "TCPIP_Illustrated_Vol-2".  A green 
+"Book deleted" status message should display at the top of the page, 
+along with a list of the eight remaining books.
+
+
+=head2 Fixing a Dangerous URL
+
+Note the URL in your browser once you have performed the deletetion in the 
+prior step -- it is still referencing the delete action:
+
+    http://localhost:3000/books/delete/6
+
+What if the user were to press reload with this URL still active?  In
+this case the redundant delete is harmless, but in other cases this
+could clearly be extremely dangerous.
+
+We can improve the logic by converting to a redirect.  Unlike
+C<$c-E<gt>forward('list'))> or C<$c-E<gt>detach('list'))> that perform
+a server-side alteration in the flow of processing, a redirect is a
+client-side mechanism that causes the brower to issue an entirely
+new request.  As a result, the URL in the browser is updated to match
+the destination of the redirection URL.
+
+To convert the forward used in the previous section to a redirect,
+open C<lib/MyApp/Controller/Books.pm> and edit the existing 
+C<sub delete> method to match:
+
+    =head2 delete 
+    
+    Delete a book
+        
+    =cut
+    
+    sub delete : Local {
+        # $id = primary key of book to delete
+        my ($self, $c, $id) = @_;
+    
+        # Search for the book and then delete it
+        $c->model('MyAppDB::Book')->search({id => $id})->delete_all;
+    
+        # Set a status message to be displayed at the top of the view
+        $c->stash->{status_msg} = "Book deleted.";
+    
+        # Redirect the user back to the list page
+        $c->response->redirect($c->uri_for('/books/list'));
+    }
+
+
+=head2 Try the Delete and Redirect Logic
+
+Restart the development server and point your browser to 
+L<http://localhost:3000/books/list>.  Delete the first copy of 
+"TCPIP_Illustrated_Vol-2", but notice that I<no green "Book deleted" 
+status message is displayed>.  Because the stash is reset on every
+request (and a redirect involves a second request), the 
+C<status_msg> is cleared before it can be displayed.
+
+
+=head2 Using C<uri_for> to Pass Query Parameters
+
+There are several ways to pass information across a redirect. 
+In general, the best option is to use the C<flash> technique that we
+will see in Part 4 of the tutorial; however, here we will pass the
+information via query parameters on the redirect itself.  Open 
+C<lib/MyApp/Controller/Books.pm> and update the existing 
+C<sub delete> method to match the following:
+
+    =head2 delete 
+    
+    Delete a book
+        
+    =cut
+    
+    sub delete : Local {
+        # $id = primary key of book to delete
+        my ($self, $c, $id) = @_;
+    
+        # Search for the book and then delete it
+        $c->model('MyAppDB::Book')->search({id => $id})->delete_all;
+    
+        # Redirect the user back to the list page with status msg as an arg
+        $c->response->redirect($c->uri_for('/books/list', 
+            {status_msg => "Book deleted."}));
+    }
+
+This modification simply leverages the ability of C<uri_for> to include
+an arbitrary number of name/value pairs in a hash reference.  Next, we 
+need to update C<root/lib/site/layout> to handle C<status_msg> as a 
+query parameter:
+
+    <div id="header">[% PROCESS site/header %]</div>
+    
+    <div id="content">
+    <span class="message">[% status_msg || Catalyst.request.params.status_msg %]</span>
+    <span class="error">[% error_msg %]</span>
+    [% content %]
+    </div>
+    
+    <div id="footer">[% PROCESS site/footer %]</div>
+
+
+=head2 Try the Delete and Redirect With Query Param Logic
+
+Restart the development server and point your browser to 
+L<http://localhost:3000/books/list>.  Then delete the remaining copy 
+of "TCPIP_Illustrated_Vol-2".  The green "Book deleted" status message
+should return.
+
+B<NOTE:> Although this did present an opportunity to show a handy
+capability of C<uri_for>, it would be much better to use Catalyst's
+C<flash> feature in this situation.  Although the technique here is 
+less dangerous than leaving the delete URL in the client's browser, 
+we have still exposed the status message to the user.  With C<flash>, 
+this message returns to its rightful place as a service-side 
+mechanism (we will migrate this code to C<flash> in the next part 
+of the tutorial).
+
+
+=head1 AUTHOR
+
+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-Runtime/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
+
diff --git a/lib/Catalyst/Manual/Tutorial/Tutorial/CatalystBasics.pod b/lib/Catalyst/Manual/Tutorial/Tutorial/CatalystBasics.pod
new file mode 100644 (file)
index 0000000..cfa1439
--- /dev/null
@@ -0,0 +1,1242 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::CatalystBasics - Catalyst Tutorial - Part 2: Catalyst Application Development Basics
+
+
+=head1 OVERVIEW
+
+This is B<Part 2 of 9> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::Intro>
+
+=item 2
+
+B<Catalyst Basics>
+
+=item 3
+
+L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
+
+=item 4
+
+L<Authentication|Catalyst::Manual::Tutorial::Authentication>
+
+=item 5
+
+L<Authorization|Catalyst::Manual::Tutorial::Authorization>
+
+=item 6
+
+L<Debugging|Catalyst::Manual::Tutorial::Debugging>
+
+=item 7
+
+L<Testing|Catalyst::Manual::Tutorial::Testing>
+
+=item 8
+
+L<Advanced CRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
+
+=item 9
+
+L<Appendices|Catalyst::Manual::Tutorial::Appendices>
+
+=back
+
+
+=head1 DESCRIPTION
+
+In this part of the tutorial, we will create a very basic Catalyst web
+application.  Though simple in many respects, this section will already
+demonstrate a number of powerful capabilities such as:
+
+=over 4
+
+=item * Helper Scripts
+
+Catalyst helper scripts that can be used to rapidly bootstrap the
+skeletal structure of an application.
+
+=item * MVC
+
+Model/View/Controller (MVC) provides an architecture that facilitates a
+clean "separation of control" between the different portions of your
+application. Given that many other documents cover this subject in
+detail, MVC will not be discussed in depth here (for an excellent
+introduction to MVC and general Catalyst concepts, please see
+L<Catalyst::Manual::About>. In short:
+
+=over 4
+
+=item * Model
+
+The model usually represents a data store. In most applications, the
+model equates to the objects that are created from and saved to your SQL
+database.
+
+=item * View
+
+The view takes model objects and renders them into something for the end
+user to look at. Normally this involves a template-generation tool that
+creates HTML for the user's web browser, but it could easily be code
+that generates other forms such as PDF documents, e-mails, or Excel
+spreadsheets.
+
+=item * Controller
+
+As suggested by its name, the controller takes user requests and routes
+them to the necessary model and view.
+
+=back
+
+=item * ORM
+
+The use of Object-Relational Mapping (ORM) technology for database
+access. Specifically, ORM provides an automated and standardized means
+to persist and restore objects to/from a relational database.
+
+=back
+
+You can checkout the source code for this example from the catalyst
+subversion repository as per the instructions in
+L<Catalyst::Manual::Tutorial::Intro>
+
+=head1 CREATE A CATALYST PROJECT
+
+Catalyst provides a number of helper scripts that can be used to quickly
+flesh out the basic structure of your application. All Catalyst projects
+begin with the C<catalyst.pl> helper (see L<Catalyst::Helper|Catalyst::Helper>
+for more information on helpers).  Also note that as of Catalyst 5.7000,
+you will not have the helper scripts unless you install both 
+L<Catalyst::Runtime|Catalyst::Runtime> and L<Catalyst::Devel|Catalyst::Devel>.
+
+In the case of this tutorial, use the Catalyst C<catalyst.pl> script to
+initialize the framework for an application called C<MyApp>:
+
+    $ catalyst.pl MyApp
+    created "MyApp"
+    created "MyApp/script"
+    created "MyApp/lib"
+    created "MyApp/root"
+    ...
+    created "MyApp/script/myapp_create.pl"
+    $ cd MyApp
+
+The C<catalyst.pl> helper script will display the names of the
+directories and files it creates.
+
+Though it's too early for any significant celebration, we already have a
+functioning application. Run the following command to run this
+application with the built-in development web server:
+
+    $ script/myapp_server.pl
+    [debug] Debug messages enabled
+    [debug] Loaded plugins:
+    .----------------------------------------------------------------------------.
+    | Catalyst::Plugin::ConfigLoader  0.13                                       |
+    | Catalyst::Plugin::Static::Simple  0.14                                     |
+    '----------------------------------------------------------------------------'
+    
+    [debug] Loaded dispatcher "Catalyst::Dispatcher"
+    [debug] Loaded engine "Catalyst::Engine::HTTP"
+    [debug] Found home "/home/me/MyApp"
+    [debug] Loaded Config "/home/me/myapp.yml"
+    [debug] Loaded components:
+    .-----------------------------------------------------------------+----------.
+    | Class                                                           | Type     |
+    +-----------------------------------------------------------------+----------+
+    | MyApp::Controller::Root                                         | instance |
+    '-----------------------------------------------------------------+----------'
+    
+    [debug] Loaded Private actions:
+    .----------------------+--------------------------------------+--------------.
+    | Private              | Class                                | Method       |
+    +----------------------+--------------------------------------+--------------+
+    | /default             | MyApp::Controller::Root              | default      |
+    | /end                 | MyApp::Controller::Root              | end          |
+    '----------------------+--------------------------------------+--------------'
+    
+    [info] MyApp powered by Catalyst 5.7002
+    You can connect to your server at http://localhost:3000
+
+B<NOTE>: Be sure you run the C<script/myapp_server.pl> command from the
+'base' directory of your application, not inside the C<script> directory 
+itself.  It doesn't make a difference at this point, but it will as soon
+as we get the database going in the next section.
+
+Point your web browser to L<http://localhost:3000> (substituting a 
+different hostname or IP address as appropriate) and you should be 
+greeted by the Catalyst welcome screen.  Information similar to the 
+following should be appended to the logging output of the development 
+server:
+
+    [info] *** Request 1 (0.043/s) [6003] [Fri Jul  7 13:32:53 2006] ***
+    [debug] "GET" request for "/" from "127.0.0.1"
+    [info] Request took 0.067675s (14.777/s)
+    .----------------------------------------------------------------+-----------.
+    | Action                                                         | Time      |
+    +----------------------------------------------------------------+-----------+
+    | /default                                                       | 0.002844s |
+    | /end                                                           | 0.000207s |
+    '----------------------------------------------------------------+-----------'
+
+Press Ctrl-C to break out of the development server.
+
+
+=head1 CREATE A SQLITE DATABASE
+
+In this step, we make a text file with the required SQL commands to
+create a database table and load some sample data.  Open C<myapp01.sql>
+in your editor and enter:
+
+    --
+    -- Create a very simple database to hold book and author information
+    --
+    CREATE TABLE books (
+            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_id     INTEGER,
+            author_id   INTEGER,
+            PRIMARY KEY (book_id, author_id)
+    );
+    CREATE TABLE authors (
+            id          INTEGER PRIMARY KEY,
+            first_name  TEXT,
+            last_name   TEXT
+    );
+    ---
+    --- 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);
+
+B<TIP>: See Appendix 1 for tips on removing the leading spaces when
+cutting and pasting example code from POD-based documents.
+
+Then use the following command to build a C<myapp.db> SQLite database:
+
+    $ sqlite3 myapp.db < myapp01.sql
+
+If you need to create the database more than once, you probably want to
+issue the C<rm myapp.db> command to delete the database before you use
+the C<sqlite3 myapp.db < myapp01.sql> command.
+
+Once the C<myapp.db> database file has been created and initialized, you
+can use the SQLite command line environment to do a quick dump of the
+database contents:
+
+    $ sqlite3 myapp.db
+    SQLite version 3.2.2
+    Enter ".help" for instructions
+    sqlite> select * from books;
+    1|CCSP SNRS Exam Certification Guide|5
+    2|TCP/IP Illustrated, Volume 1|5
+    3|Internetworking with TCP/IP Vol.1|4
+    4|Perl Cookbook|5
+    5|Designing with Web Standards|5
+    sqlite> .q
+    $
+
+Or:
+
+    $ sqlite3 myapp.db "select * from books"
+    1|CCSP SNRS Exam Certification Guide|5
+    2|TCP/IP Illustrated, Volume 1|5
+    3|Internetworking with TCP/IP Vol.1|4
+    4|Perl Cookbook|5
+    5|Designing with Web Standards|5
+
+As with most other SQL tools, if you are using the full "interactive"
+environment you need to terminate your SQL commands with a ";" (it's not
+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.
+
+
+=head1 EDIT THE LIST OF CATALYST PLUGINS
+
+One of the greatest benefits of Catalyst is that it has such a large
+library of plugins available.  Plugins are used to seamlessly integrate
+existing Perl modules into the overall Catalyst framework.  In general,
+they do this by adding additional methods to the C<context> object
+(generally written as C<$c>) that Catalyst passes to every component
+throughout the framework.
+
+By default, Catalyst enables three plugins/flags:
+
+=over 4
+
+=item * 
+
+C<-Debug> Flag
+
+Enables the Catalyst debug output you saw when we started the
+C<script/myapp_server.pl> development server earlier.  You can remove
+this plugin when you place your application into production.
+
+As you may have noticed, C<-Debug> is not a plugin, but a I<flag>.
+Although most of the items specified on the C<use Catalyst> line of your
+application class will be plugins, Catalyst supports a limited number of
+flag options (of these, C<-Debug> is the most common).  See the
+documentation for C<Catalyst.pm> to get details on other flags 
+(currently C<-Engine>, C<-Home>, and C<-Log>).
+
+If you prefer, you can use the C<$c-E<gt>debug> method to enable debug
+messages.
+
+=item *
+
+L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader>
+
+C<ConfigLoader> provides an automatic way to load configurable
+parameters for your application from a central YAML file (versus having
+the values hard-coded inside your Perl modules).  If you have not been
+exposed to YAML before, it is a human-readable data serialization format
+that can be used to read (and write) values to/from text files.  We will
+see how to use this feature of Catalyst during the authentication and
+authorization sections (Part 4 and Part 5).
+
+=item *
+
+L<Catalyst::Plugin::Static::Simple|Catalyst::Plugin::Static::Simple>
+
+C<Static::Simple> provides an easy method of serving static content such
+as images and CSS files under the development server.
+
+=back
+
+To modify the list of plugins, edit C<lib/MyApp.pm> (this file is
+generally referred to as your I<application class>) and delete the line
+with:
+
+    use Catalyst qw/-Debug ConfigLoader Static::Simple/;
+
+Replace it with:
+
+    use Catalyst qw/
+            -Debug
+            ConfigLoader
+            Static::Simple
+            
+            StackTrace
+            /;
+
+This tells Catalyst to start using one new plugin:
+
+=over 4
+
+=item * 
+
+L<Catalyst::Plugin::StackTrace|Catalyst::Plugin::StackTrace>
+
+Adds a stack trace to the standard Catalyst "debug screen" (this is the
+screen Catalyst sends to your browser when an error occurs).
+
+Note: 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.
+
+=back
+
+Note that when specifying plugins on the C<use Catalyst> line, you can
+omit C<Catalyst::Plugin::> from the name.  Additionally, you can spread
+the plugin names across multiple lines as shown here, or place them all
+on one (or more) lines as with the default configuration.
+
+B<TIP:> You may see examples that include the
+L<Catalyst::Plugin::DefaultEnd|Catalyst::Plugin::DefaultEnd>
+plugins.  As of Catalyst 5.7000, C<DefaultEnd> has been
+deprecated in favor of 
+L<Catalyst::Action::RenderView|Catalyst::Action::RenderView>
+(as the name of the package suggests, C<RenderView> is not
+a plugin, but an action). The purpose of both is essentially the same: 
+forward processing to the view to be rendered.  Applications generated
+under 5.7000 should automatically use C<RenderView> and "just work"
+for most applications.  For more information on C<RenderView> and 
+the various options for forwarding to your view logic, please refer 
+to the "Using RenderView for the Default View" section under 
+"CATALYST VIEWS" below.
+
+
+=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 the traditional 
+Perl ORM engine, 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.
+
+Note: See L<Catalyst::Model::CDBI> for more information on using
+Catalyst with L<Class::DBI|Class::DBI>.
+
+=head2 Create a DBIC Schema File
+
+DBIx::Class uses a schema file to load other classes that represent the
+tables in your database (DBIC refers to these "table objects" as "result
+sources"; see L<DBIx::Class::ResultSource>).  In this case, we want to
+load the model object for the C<books>, C<book_authors>, and C<authors>
+tables created in the previous step.
+
+Create C<lib/MyAppDB.pm> in your editor and insert:
+
+    package MyAppDB;
+    
+    =head1 NAME 
+    
+    MyAppDB - DBIC Schema Class
+    
+    =cut
+    
+    # Our schema needs to inherit from 'DBIx::Class::Schema'
+    use base qw/DBIx::Class::Schema/;
+    
+    # Need to load the DB Model classes here.
+    # You can use this syntax if you want:
+    #    __PACKAGE__->load_classes(qw/Book BookAuthor Author/);
+    # Also, if you simply want to load all of the classes in a directory
+    # of the same name as your schema class (as we do here) you can use:
+    #    __PACKAGE__->load_classes(qw//);
+    # But the variation below is more flexible in that it can be used to 
+    # load from multiple namespaces.
+    __PACKAGE__->load_classes({
+        MyAppDB => [qw/Book BookAuthor Author/]
+    });
+    
+    1;
+
+B<Note:> C<__PACKAGE__> is just a shorthand way of referencing the name
+of the package where it is used.  Therefore, in C<MyAppDB.pm>,
+C<__PACKAGE__> is equivalent to C<MyAppDB>.
+
+B<Note:> 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 as shown above.
+
+
+=head2 Create the DBIC "Result Source" Files
+
+In this step, we create "table classes" (again, these are called a
+"result source" classes in DBIC) that act as model objects for the
+C<books>, C<book_authors>, and C<authors> tables in our database.
+
+First, create a directory to hold the class:
+
+    $ mkdir lib/MyAppDB
+
+Then create C<lib/MyAppDB/Book.pm> in your editor and enter:
+
+    package MyAppDB::Book;
+    
+    use base qw/DBIx::Class/;  
+    
+    # Load required DBIC stuff
+    __PACKAGE__->load_components(qw/PK::Auto Core/);
+    # Set the table name
+    __PACKAGE__->table('books');
+    # Set columns in table
+    __PACKAGE__->add_columns(qw/id title rating/);
+    # Set the primary key for the table
+    __PACKAGE__->set_primary_key(qw/id/);
+    
+    #
+    # Set relationships:
+    #
+    
+    # has_many():
+    #   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
+    __PACKAGE__->has_many(book_authors => 'MyAppDB::BookAuthor', 'book_id');
+    
+    # many_to_many():
+    #   args:
+    #     1) Name of relationship, DBIC will create accessor with this name
+    #     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');
+    
+    
+    =head1 NAME
+    
+    MyAppDB::Book - A model object representing a book.
+    
+    =head1 DESCRIPTION
+    
+    This is an object that represents a row in the 'books' table of your application
+    database.  It uses DBIx::Class (aka, DBIC) to do ORM.
+    
+    For Catalyst, this is designed to be used through MyApp::Model::MyAppDB.
+    Offline utilities may wish to use this class directly.
+    
+    =cut
+    
+    1;
+
+This 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
+will see examples on how to use DBIC objects in your code soon, but note
+that because C<$book-E<gt>book_authors> 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-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.
+
+Next, create C<lib/MyAppDB/Author.pm> in your editor and enter:
+
+    package MyAppDB::Author;
+    
+    use base qw/DBIx::Class/;
+    
+    # Load required DBIC stuff
+    __PACKAGE__->load_components(qw/PK::Auto Core/);
+    # Set the table name
+    __PACKAGE__->table('authors');
+    # Set columns in table
+    __PACKAGE__->add_columns(qw/id first_name last_name/);
+    # Set the primary key for the table
+    __PACKAGE__->set_primary_key(qw/id/);
+    
+    #
+    # Set relationships:
+    #
+    
+    # has_many():
+    #   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
+    __PACKAGE__->has_many(book_author => 'MyAppDB::BookAuthor', 'author_id');
+    
+    # many_to_many():
+    #   args:
+    #     1) Name of relationship, DBIC will create accessor with this name
+    #     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');
+    
+    
+    =head1 NAME
+    
+    MyAppDB::Author - A model object representing an author of a book (if a book has 
+    multiple authors, each will be represented be separate Author object).
+    
+    =head1 DESCRIPTION
+    
+    This is an object that represents a row in the 'authors' table of your application
+    database.  It uses DBIx::Class (aka, DBIC) to do ORM.
+    
+    For Catalyst, this is designed to be used through MyApp::Model::MyAppDB.
+    Offline utilities may wish to use this class directly.
+    
+    =cut
+    
+    1;
+
+Finally, create C<lib/MyAppDB/BookAuthor.pm> in your editor and enter:
+
+    package MyAppDB::BookAuthor;
+    
+    use base qw/DBIx::Class/;
+    
+    # Load required DBIC stuff
+    __PACKAGE__->load_components(qw/PK::Auto Core/);
+    # Set the table name
+    __PACKAGE__->table('book_authors');
+    # Set columns in table
+    __PACKAGE__->add_columns(qw/book_id author_id/);
+    # Set the primary key for the table
+    __PACKAGE__->set_primary_key(qw/book_id author_id/);
+    
+    #
+    # Set relationships:
+    #
+    
+    # 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(book => 'MyAppDB::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 => 'MyAppDB::Author', 'author_id');
+    
+    
+    =head1 NAME
+    
+    MyAppDB::BookAuthor - A model object representing the JOIN between an author and 
+    a book.
+    
+    =head1 DESCRIPTION
+    
+    This is an object that represents a row in the 'book_authors' table of your 
+    application database.  It uses DBIx::Class (aka, DBIC) to do ORM.
+    
+    You probably won't need to use this class directly -- it will be automatically
+    used by DBIC where joins are needed.
+    
+    For Catalyst, this is designed to be used through MyApp::Model::MyAppDB.
+    Offline utilities may wish to use this class directly.
+    
+    =cut
+    
+    1;
+
+B<Note:> This sample application uses a plural form for the database
+tables (e.g., C<books> and C<authors>) and a singular form for the model
+objects (e.g., C<Book> and C<Author>); however, Catalyst places no
+restrictions on the naming conventions you wish to use.
+
+=head2 Use C<Catalyst::Model::DBIC::Schema> To Load The Model Class
+
+When L<Catalyst::Model::DBIC::Schema|Catalyst::Model::DBIC::Schema> is
+in use, Catalyst essentially reads an existing copy of your database
+model and creates a new set of objects under C<MyApp::Model> for use
+inside of Catalyst.
+
+B<Note:> With 
+L<Catalyst::Model::DBIC::Schema|Catalyst::Model::DBIC::Schema> you 
+essentially end up with two sets of model classes (only one of which 
+you write... the other set is created automatically in memory when 
+your Catalyst application initializes).  For this tutorial application, 
+the important points to remember are: you write the I<result source> 
+files in C<MyAppDB>, but I<within Catalyst> you use the I<automatically 
+created model classes> in C<MyApp::Model>.
+
+Use the 
+L<Catalyst::Helper::Model::DBIC::Schema|Catalyst::Helper::Model::DBIC::Schema> 
+helper script to create the model class that loads up the model we 
+created in the previous step:
+
+    $ script/myapp_create.pl model MyAppDB DBIC::Schema MyAppDB dbi:SQLite:myapp.db '' '' '{ AutoCommit => 1 }'
+     exists "/root/dev/MyApp/script/../lib/MyApp/Model"
+     exists "/root/dev/MyApp/script/../t"
+    created "/root/dev/MyApp/script/../lib/MyApp/Model/MyAppDB.pm"
+    created "/root/dev/MyApp/script/../t/model_MyAppDB.t"
+
+
+Where the first C<MyAppDB> is the name of the class to be created by the
+helper in C<lib/MyApp/Model> and the second C<MyAppDB> is the name of
+existing schema file we created (in C<lib/MyAppDB.pm>).  You can see
+that the helper creates a model file under C<lib/MyApp/Model> (Catalyst
+has a separate directory under C<lib/MyApp> for each of the three parts
+of MVC: C<Model>, C<View>, and C<Controller> [although older Catalyst
+applications often use the directories C<M>, C<V>, and C<C>]).
+
+
+=head1 CREATE A CATALYST CONTROLLER
+
+Controllers are where you write methods that interact with user
+input--typically, controller methods respond to C<GET> and C<POST>
+messages from the user's web browser.
+
+Use the Catalyst C<create> script to add a controller for book-related
+actions:
+
+    $ script/myapp_create.pl controller Books
+     exists "/root/dev/MyApp/script/../lib/MyApp/Controller"
+     exists "/root/dev/MyApp/script/../t"
+    created "/root/dev/MyApp/script/../lib/MyApp/Controller/Books.pm"
+    created "/root/dev/MyApp/script/../t/controller_Books.t"
+
+Then edit C<lib/MyApp/Controller/Books.pm> and add the following method
+to the controller:
+
+    =head2 list
+    
+    Fetch all book objects and pass to books/list.tt2 in stash to be displayed
+    
+    =cut
+     
+    sub list : Local {
+        # Retrieve the usual perl OO '$self' for this object. $c is the Catalyst
+        # 'Context' that's used to 'glue together' the various components
+        # that make up the application
+        my ($self, $c) = @_;
+    
+        # 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('MyAppDB::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
+        # your controllers).
+        $c->stash->{template} = 'books/list.tt2';
+    }
+
+B<Note:> Programmers experienced with object-oriented Perl should
+recognize C<$self> as a reference to the object where this method was
+called.  On the other hand, C<$c> will be new to many Perl programmers
+who have not used Catalyst before (it's sometimes written as
+C<$context>).  The 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<TIP>: You may see the C<$c-E<gt>model('MyAppDB::Book')> used above
+written as C<$c-E<gt>model('MyAppDB')-E<gt>resultset('Book)>.  The two
+are equivalent.
+
+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.
+
+
+=head1 CATALYST VIEWS
+
+Views are where you render output, typically for display in the user's
+web browser, but also possibly using other display output-generation
+systems.  As with virtually every aspect of Catalyst, options abound
+when it comes to the specific view technology you adopt inside your
+application.  However, most Catalyst applications use the Template
+Toolkit, known as TT (for more information on TT, see
+L<http://www.template-toolkit.org>). Other popular view technologies
+include Mason (L<http://www.masonhq.com> and
+L<http://www.masonbook.com>) and L<HTML::Template|HTML::Template>
+(L<http://html-template.sourceforge.net>).
+
+=head2 Create a Catalyst View Using C<TTSite>
+
+When using TT for the Catalyst view, there are two main helper scripts:
+
+=over 4
+
+=item *
+
+L<Catalyst::Helper::View::TT|Catalyst::Helper::View::TT>
+
+=item *
+
+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>
+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 7). The C<TTSite> helper 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.
+
+Enter the following command to enable the C<TTSite> style of view
+rendering for this tutorial:
+
+    $ script/myapp_create.pl view TT TTSite
+     exists "/root/dev/MyApp/script/../lib/MyApp/View"
+     exists "/root/dev/MyApp/script/../t"
+    created "/root/dev/MyApp/script/../lib/MyApp/View/TT.pm"
+    created "/root/dev/MyApp/script/../root/lib"
+    ...
+    created "/root/dev/MyApp/script/../root/src/ttsite.css"
+
+This puts a number of files in the C<root/lib> and C<root/src>
+directories that can be used to customize the look and feel of your
+application.  Also take a look at C<lib/MyApp/View/TT.pm> for config
+values set by the C<TTSite> helper.
+
+B<TIP>: Note that TTSite does one thing that could confuse people who
+are used to the normal C<TT> Catalyst view: it redefines the Catalyst
+context object in templates from its usual C<c> to C<Catalyst>. When
+looking at other Catalyst examples, remember that they almost always use
+C<c>.  Note that Catalyst and TT I<do not complain> when you use the
+wrong name to access the context object...TT simply outputs blanks for
+that bogus logic (see next tip to change this behavior with TT C<DEBUG>
+options).  Finally, be aware that this change in name I<only>
+applies to how the context object is accessed inside your TT templates;
+your controllers will continue to use C<$c> (or whatever name you use
+when fetching the reference from C<@_> inside your methods). (You can
+change back to the "default" behavior be removing the C<CATALYST_VAR>
+line from C<lib/MyApp/View/TT.pm>, but you will also have to edit
+C<root/lib/config/main> and C<root/lib/config/url>.  If you do this, be
+careful not to have a collision between your own C<c> variable and the
+Catalyst C<c> variable.)
+
+B<TIP>: When troubleshooting TT it can be helpful to enable variable
+C<DEBUG> options.  You can do this in a Catalyst environment by adding
+a C<DEBUG> line to the C<__PACKAGE__->config> declaration in 
+C<lib/MyApp/View/TT.pm>:
+
+    __PACKAGE__->config({
+        CATALYST_VAR => 'Catalyst',
+        ...
+        DEBUG        => 'undef',
+        ...
+    });
+
+There are a variety of options you can use, such as 'undef', 'all', 
+'service', 'context', 'parser', 'provider', and 'service'.  See
+L<Template::Constants> for more information (remove the C<DEBUG_>
+portion of the name shown in the TT docs and convert to lower case
+for use inside Catalyst).
+
+B<NOTE:> Please be sure to disable TT debug options before 
+continuing the tutorial (especially the 'undef' option -- leaving
+this enabled will conflict with several of the conventions used
+by this tutorial and TTSite to leave some variables undefined
+on purpose).
+
+
+=head2 Using C<RenderView> for the Default View
+
+Once your controller logic has processed the request from a user, it 
+forwards processing to your view in order to generate the appropriate 
+response output.  Catalyst v5.7000 ships with a new mechanism, 
+L<Catalyst::Action::RenderView|Catalyst::Action::RenderView>, that 
+automatically performs this operation.  If you look in 
+C<lib/MyApp/Controller/Root.pm>, you should see the empty 
+definition for the C<sub end> method:
+
+    sub end : ActionClass('RenderView') {}
+
+The following bullet points provide a quick overview of the 
+C<RenderView> process:
+
+=over 4
+
+=item *
+
+C<Root.pm> is designed to hold application-wide logic.
+
+=item *
+
+At the end of a given user request, Catalyst will call the most specific 
+C<end> method that's appropriate.  For example, if the controller for a 
+request has an C<end> method defined, it will be called.  However, if 
+the controller does not define a controller-specific C<end> method, the 
+"global" C<end> method in C<Root.pm> will be called.
+
+=item *
+
+Because the definition includes an C<ActionClass> attribute, the
+L<Catalyst::Action::RenderView|Catalyst::Action::RenderView> logic
+will be executed B<after> any code inside the definition of C<sub end>
+is run.  See L<Catalyst::Manual::Actions|Catalyst::Manual::Actions>
+for more information on C<ActionClass>.
+
+=item *
+
+Because C<sub end> is empty, this effectively just runs the default 
+logic in C<RenderView>.  However, you can easily extend the 
+C<RenderView> logic by adding your own code inside the empty method body 
+(C<{}>) created by the Catalyst Helpers when we first ran the 
+C<catalyst.pl> to initialize our application.  See 
+L<Catalyst::Action::RenderView|Catalyst::Action::RenderView> for more 
+detailed information on how to extended C<RenderView> in C<sub end>.
+
+=back
+
+
+=head3 The History Leading Up To C<RenderView>
+
+Although C<RenderView> strikes a nice balance between default
+behavior and easy extensibility, it is a new feature that won't 
+appear in most existing Catalyst examples.  This section provides
+some brief background on the evolution of default view rendering
+logic with an eye to how they can be migrated to C<RenderView>:
+
+=over 4
+
+=item *
+
+Private C<end> Action in Application Class
+
+Older Catalyst-related documents often suggest that you add a "private 
+end action" to your application class (C<MyApp.pm>) or Root.pm 
+(C<MyApp/Controller/Root.pm>).  These examples should be easily 
+converted to L<RenderView|Catalyst::Action::RenderView> by simply adding 
+the attribute C<:ActionClass('RenderView')> to the C<sub end> 
+definition. If end sub is defined in your application class 
+(C<MyApp.pm>), you should also migrate it to 
+C<MyApp/Controller/Root.pm>.
+
+=item *
+
+L<Catalyst::Plugin::DefaultEnd|Catalyst::Plugin::DefaultEnd>
+
+C<DefaultEnd> represented the "next step" in passing processing from 
+your controller to your view.  It has the advantage of only requiring 
+that C<DefaultEnd> be added to the list of plugins in C<lib/MyApp.pm>. 
+It also allowed you to add "dump_info=1" (precede with "?" or "&" 
+depending on where it is in the URL) to I<force> the debug screen at the 
+end of the Catalyst request processing cycle.  However, it was more 
+difficult to extend than the C<RenderView> mechanism, and is now 
+deprecated.
+
+=item *
+
+L<Catalyst::Action::RenderView|Catalyst::Action::RenderView>
+
+As discussed above, the current recommended approach to handling your 
+view logic relies on 
+L<Catalyst::Action::RenderView|Catalyst::Action::RenderView>.  Although 
+similar in first appearance to the "private end action" approach, it 
+utilizes Catalyst's "ActionClass" mechanism to provide both automatic 
+default behavior (you don't have to include a plugin as with 
+C<DefaultEnd>) and easy extensibility.  As with C<DefaultEnd>, it allows 
+you to add "dump_info=1" (precede with "?" or "&" depending on where it 
+is in the URL) to I<force> the debug screen at the end of the Catalyst 
+request processing cycle.
+
+=back
+
+It is recommended that all Catalyst applications use or migrate to
+the C<RenderView> approach.
+
+
+=head2 Globally Customize Every View
+
+When using TTSite, files in the subdirectories of C<root/lib> can be
+used to make changes that will appear in every view.  For example, to
+display optional status and error messages in every view, edit
+C<root/lib/site/layout>, updating it to match the following (the two HTML
+C<span> elements are new):
+
+    <div id="header">[% PROCESS site/header %]</div>
+    
+    <div id="content">
+    <span class="message">[% status_msg %]</span>
+    <span class="error">[% error_msg %]</span>
+    [% content %]
+    </div>
+    
+    <div id="footer">[% PROCESS site/footer %]</div>
+
+If we set either message in the Catalyst stash (e.g.,
+C<$c-E<gt>stash-E<gt>{status_msg} = 'Request was successful!'>) it will
+be displayed whenever any view used by that request is rendered.  The
+C<message> and C<error> CSS styles are automatically defined in
+C<root/src/ttsite.css> and can be customized to suit your needs.
+
+B<Note:> The Catalyst stash only lasts for a single HTTP request.  If
+you need to retain information across requests you can use
+L<Catalyst::Plugin::Session|Catalyst::Plugin::Session> (we will use
+Catalyst sessions in the Authentication part of the tutorial).
+
+
+=head2 Create a TT Template Page
+
+To add a new page of content to the TTSite view hierarchy, just create a
+new C<.tt2> file in C<root/src>.  Only include HTML markup that goes
+inside the HTML <body> and </body> tags, TTSite will use the contents of
+C<root/lib/site> to add the top and bottom.
+
+First create a directory for book-related TT templates:
+
+    $ mkdir root/src/books
+
+Then create C<root/src/books/list.tt2> in your editor and enter:
+
+    [% # This is a TT comment.  The '-' at the end "chomps" the newline.  You won't -%]
+    [% # see this "chomping" in your browser because HTML ignores blank lines, but  -%]
+    [% # it WILL eliminate a blank line if you view the HTML source.  It's purely   -%]
+    [%- # optional, but both the beginning and the ending TT tags support chomping. -%]
+    
+    [% # Provide a title to root/lib/site/header -%]
+    [% META title = 'Book List' -%]
+    
+    <table>
+    <tr><th>Title</th><th>Rating</th><th>Author(s)</th></tr>
+    [% # Display each book in a table row %]
+    [% FOREACH book IN books -%]
+      <tr>
+        <td>[% book.title %]</td>
+        <td>[% book.rating %]</td>
+        <td>
+          [% # First initialize a TT variable to hold a list.  Then use a TT FOREACH -%]
+          [% # loop in 'side effect notation' to load just the last names of the     -%]
+          [% # authors into the list.  Note that the 'push' TT vmethod does not      -%]
+          [% # a value, so nothing will be printed here.  But, if you have something -%]
+          [% # in TT that does return a method 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.                                 -%]
+          [% tt_authors = [ ];
+             tt_authors.push(author.last_name) FOREACH author = book.authors %]
+          [% # Now use a TT 'virtual method' to display the author count in parens   -%]
+          ([% tt_authors.size %])
+          [% # Use another TT vmethod to join & print the names & comma separators   -%]
+          [% tt_authors.join(', ') %]
+        </td>
+      </tr>
+    [% END -%]
+    </table>
+
+As indicated by the inline comments above, the C<META title> line uses
+TT's META feature to provide a title to C<root/lib/site/header>.
+Meanwhile, the outer C<FOREACH> loop iterates through each C<book> model
+object and prints the C<title> and C<rating> fields.  An inner
+C<FOREACH> loop prints the last name of each author in a comma-separated
+list within a single table cell.
+
+If you are new to TT, the C<[%> and C<%]> tags are used to delimit TT
+code.  TT supports a wide variety of directives for "calling" other
+files, looping, conditional logic, etc.  In general, TT simplifies the
+usual range of Perl operators down to the single dot (C<.>) operator.
+This applies to operations as diverse as method calls, hash lookups, and
+list index values (see
+L<http://www.template-toolkit.org/docs/default/Manual/Variables.html>
+for details and examples).  In addition to the usual C<Template> module
+Pod documentation, you can access the TT manual at
+L<http://www.template-toolkit.org/docs/default/>.
+
+B<NOTE>: The C<TTSite> helper creates several TT files using an
+extension of C<.tt2>. Most other Catalyst and TT examples use an
+extension of C<.tt>.  You can use either extension (or no extension at
+all) with TTSite and TT, just be sure to use the appropriate extension
+for both the file itself I<and> the C<$c-E<gt>stash-E<gt>{template} =
+...> line in your controller.  This document will use C<.tt2> for
+consistency with the files already created by the C<TTSite> helper.
+
+
+=head1 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):
+
+    $ export DBIC_TRACE=1
+
+B<NOTE>: You can also use the older 
+C<export DBIX_CLASS_STORAGE_DBI_DEBUG=1>, but that's a lot more to
+type.
+
+This assumes you are using BASH as your shell -- adjust accordingly if
+you are using a different shell (for example, under tcsh, use
+C<setenv DBIX_CLASS_STORAGE_DBI_DEBUG 1>).
+
+B<NOTE>: You can also set this in your code using
+C<$class-E<gt>storage-E<gt>debug(1);>.  See
+L<DBIx::Class::Manual::Troubleshooting> for details (including options
+to log to file instead of displaying to the Catalyst development server
+log).
+
+Then run the Catalyst "demo server" script:    
+
+    $ script/myapp_server.pl
+
+Your development server log output should display something like:
+
+    $ script/myapp_server.pl
+    [debug] Debug messages enabled
+    [debug] Loaded plugins:
+    .----------------------------------------------------------------------------.
+    | Catalyst::Plugin::ConfigLoader  0.13                                       |
+    | Catalyst::Plugin::StackTrace  0.06                                         |
+    | Catalyst::Plugin::Static::Simple  0.14                                     |
+    '----------------------------------------------------------------------------'
+    
+    [debug] Loaded dispatcher "Catalyst::Dispatcher"
+    [debug] Loaded engine "Catalyst::Engine::HTTP"
+    [debug] Found home "/home/me/MyApp"
+    [debug] Loaded Config "/home/me/myapp.yml"
+    [debug] Loaded components:
+    .-----------------------------------------------------------------+----------.
+    | Class                                                           | Type     |
+    +-----------------------------------------------------------------+----------+
+    | MyApp::Controller::Books                                        | instance |
+    | MyApp::Controller::Root                                         | instance |
+    | MyApp::Model::MyAppDB                                           | instance |
+    | MyApp::Model::MyAppDB::Author                                   | class    |
+    | MyApp::Model::MyAppDB::Book                                     | class    |
+    | MyApp::Model::MyAppDB::BookAuthor                               | class    |
+    | MyApp::View::TT                                                 | instance |
+    '-----------------------------------------------------------------+----------'
+    
+    [debug] Loaded Private actions:
+    .----------------------+--------------------------------------+--------------.
+    | Private              | Class                                | Method       |
+    +----------------------+--------------------------------------+--------------+
+    | /default             | MyApp::Controller::Root              | default      |
+    | /end                 | MyApp::Controller::Root              | end          |
+    | /books/index         | MyApp::Controller::Books             | index        |
+    | /books/list          | MyApp::Controller::Books             | list         |
+    '----------------------+--------------------------------------+--------------'
+    
+    [debug] Loaded Path actions:
+    .-------------------------------------+--------------------------------------.
+    | Path                                | Private                              |
+    +-------------------------------------+--------------------------------------+
+    | /books/list                         | /books/list                          |
+    '-------------------------------------+--------------------------------------'
+    
+    [info] MyApp powered by Catalyst 5.7002
+    You can connect to your server at http://localhost:3000
+
+Some things you should note in the output above:
+
+=over 4
+
+=item * 
+
+Catalyst::Model::DBIC::Schema took our C<MyAppDB::Book> and made it
+C<MyApp::Model::MyAppDB::Book> (and similar actions were performed on
+C<MyAppDB::Author> and C<MyAppDB::BookAuthor>).
+
+=item * 
+
+The "list" action in our Books controller showed up with a path of
+C</books/list>.
+
+=back
+
+Point your browser to L<http://localhost:3000> and you should still get
+the Catalyst welcome page.
+
+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, with TTSite providing
+the formatting for the very simple output we generated in our template.
+The count and space-separated list of author last names appear on the
+end of each row.
+
+Also notice in the output of the C<script/myapp_server.pl> that DBIC
+used the following SQL to retrieve the data:
+
+    SELECT me.id, me.title, me.rating FROM books me
+
+Along with a list of the following commands to retrieve the authors for
+each book (the lines have been "word wrapped" here to improve
+legibility):
+
+    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'
+
+You should see 5 such lines of debug output as DBIC fetches the author 
+information for each book.
+
+
+=head1 USING THE DEFAULT TEMPLATE NAME
+
+By default, C<Catalyst::View::TT> will look for a template that uses the 
+same name as your controller action, allowing you to save the step of 
+manually specifying the template name in each action.  For example, this 
+would allow us to remove the 
+C<$c-E<gt>stash-E<gt>{template} = 'books/list.tt2';> line of our 
+C<list> action in the Books controller.  Open 
+C<lib/MyApp/Controller/Books.pm> in your editor and comment out this line
+to match the following (only the C<$c-E<gt>stash-E<gt>{template}> line
+has changed):
+
+    =head2 list
+    
+    Fetch all book objects and pass to books/list.tt2 in stash to be displayed
+    
+    =cut
+    
+    sub list : Local {
+        # Retrieve the usual perl OO '$self' for this object. $c is the Catalyst
+        # 'Context' that's used to 'glue together' the various components
+        # that make up the application
+        my ($self, $c) = @_;
+    
+        # 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('MyAppDB::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
+        # your controllers).
+        #$c->stash->{template} = 'books/list.tt2';
+    }
+
+C<Catalyst::View::TT> defaults to looking for a template with no 
+extension.  In our case, we need to override this to look for an 
+extension of C<.tt2>.  Open C<lib/MyApp/View/TT.pm> and add the 
+C<TEMPLATE_EXTENSION> definition as follows:
+
+    __PACKAGE__->config({
+        CATALYST_VAR => 'Catalyst',
+        INCLUDE_PATH => [
+            MyApp->path_to( 'root', 'src' ),
+            MyApp->path_to( 'root', 'lib' )
+        ],
+        PRE_PROCESS  => 'config/main',
+        WRAPPER      => 'site/wrapper',
+        ERROR        => 'error.tt2',
+        TIMER        => 0,
+        TEMPLATE_EXTENSION => '.tt2',
+    });
+
+You should now be able to restart the development server as per the 
+previous section and access the L<http://localhost:3000/books/list>
+as before.
+
+B<NOTE:> Please note that if you use the default template technique,
+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 Part 2 and 
+Part 8 of the Tutorial).
+
+
+=head1 RETURN TO A MANUALLY-SPECIFIED TEMPLATE
+
+In order to be able to use C<$c-E<gt>forward> and C<$c-E<gt>detach>
+later in the tutorial, you should remove the comment from the
+statement in C<sub list>:
+
+    $c->stash->{template} = 'books/list.tt2';
+
+Then delete the C<TEMPLATE_EXTENSION> line in  
+C<lib/MyApp/View/TT.pm>.
+
+You should then be able to restart the development server and 
+access L<http://localhost:3000/books/list> in the same manner as
+with earlier sections.
+
+
+=head1 AUTHOR
+
+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-Runtime/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
+
diff --git a/lib/Catalyst/Manual/Tutorial/Tutorial/Debugging.pod b/lib/Catalyst/Manual/Tutorial/Tutorial/Debugging.pod
new file mode 100644 (file)
index 0000000..6a958d3
--- /dev/null
@@ -0,0 +1,303 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::Debugging - Catalyst Tutorial - Part 6: Debugging
+
+=head1 OVERVIEW
+
+This is B<Part 6 of 9> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
+
+=item 3
+
+L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>
+
+=item 4
+
+L<Authentication|Catalyst::Manual::Tutorial::Authentication>
+
+=item 5
+
+L<Authorization|Catalyst::Manual::Tutorial::Authorization>
+
+=item 6
+
+B<Debugging>
+
+=item 7
+
+L<Testing|Catalyst::Manual::Tutorial::Testing>
+
+=item 8
+
+L<AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
+
+=item 9
+
+L<Appendices|Catalyst::Manual::Tutorial::Appendices>
+
+=back
+
+
+=head1 DESCRIPTION
+
+This part of the tutorial takes a brief look at the primary options
+available for troubleshooting Catalyst applications.
+
+Note that when it comes to debugging and troubleshooting, there are two
+camps:
+
+=over 4
+
+=item * 
+
+Fans of C<log> and C<print> statements embedded in the code.
+
+=item * 
+
+Fans of interactive debuggers.
+
+=back
+
+Catalyst is able to easily accommodate both styles of debugging.
+
+=head1 LOG STATEMENTS
+
+Folks in the former group can use Catalyst's C<$c-E<gt>log> facility.
+(See L<Catalyst::Log> for more detail.) For example, if you add the
+following code to a controller action method:
+
+    $c->log->info("Starting the foreach loop here");
+
+    $c->log->debug("Value of $id is: ".$id);
+
+Then the Catalyst development server will display your message along
+with the other debug output. To accomplish the same thing in a TTSite
+view use:
+
+    [% Catalyst.log.debug("This is a test log message") %]
+
+You can also use L<Data::Dumper|Data::Dumper> in both Catalyst code 
+(C<use Data::Dumper; $c-E<gt>log-E<gt>debug("$var is: ".Dumper($var));)>) 
+and TT templates (C<[% Dumper.dump(book) %]>.
+
+=head1 RUNNING CATALYST UNDER THE PERL DEBUGGER
+
+Members of the interactive-debugger fan club will also be at home with
+Catalyst applications.  One approach to this style of Perl debugging is
+to embed breakpoints in your code.  For example, open
+C<lib/MyApp/Controller/Books.pm> in your editor and add the
+C<DB::single=1> line as follows inside the C<list> method (I like to
+"left-justify" my debug statements so I don't forget to remove them, but
+you can obviously indent them if you prefer):
+
+    sub list : Local {
+        # Retrieve the usual perl OO '$self' for this object. $c is the Catalyst
+        # 'Context' that's used to 'glue together' the various components
+        # that make up the application
+        my ($self, $c) = @_;
+    
+    $DB::single=1;
+            
+        # 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('MyAppDB::Book')->all];
+            
+        # Set the TT template to use.  You will almost always want to do this
+        # in your action methods.
+        $c->stash->{template} = 'books/list.tt2';
+    }
+
+This causes the Perl Debugger to enter "single step mode" when this command is 
+encountered (it has no effect when Perl is run without the C<-d> flag).
+
+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>:
+
+    $ perl -d script/myapp_server.pl
+
+This will start the interactive debugger and produce output similar to:
+
+    $ perl -d script/myapp_server.pl  
+    
+    Loading DB routines from perl5db.pl version 1.27
+    Editor support available.
+    
+    Enter h or `h h' for help, or `man perldebug' for more help.
+    
+    main::(script/myapp_server.pl:14):      my $debug         = 0;
+    
+      DB<1> 
+
+Press the C<c> key and hit C<Enter> to continue executing the Catalyst
+development server under the debugger.  Although execution speed will be
+slightly slower than normal, you should soon see the usual Catalyst
+startup debug information.
+
+Now point your browser to L<http://localhost:3000/books/list> and log
+in.  Once the breakpoint is encountered in the
+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:40):
+    40:         $c->stash->{books} = [$c->model('MyAppDB::Book')->all];
+    
+      DB<1>
+
+You now have the full Perl debugger at your disposal.  First use the
+C<next> feature by typing C<n> to execute the C<all> method on the Book
+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:
+    MyApp::Controller::Books::list(/home/me/MyApp/script/../lib/MyApp/Controller/Books.pm:44):
+    44:         $c->stash->{template} = 'books/list.tt2';
+    
+      DB<1>
+
+This takes you to the next line of code where the template name is set.
+Notice that because we enabled C<DBIC_TRACE=1> earlier, SQL debug 
+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('MyAppDB::Book')
+    ()
+    (0+
+    (bool
+    MODIFY_CODE_ATTRIBUTES
+    _attr_cache
+    _collapse_result
+    _construct_object
+    _count
+    _result_class_accessor
+    _result_source_accessor
+    all
+    carp
+    <lines removed for brevity>
+    
+      DB<2>
+
+We can also play with the model directly:
+
+      DB<2> x ($c->model('MyAppDB::Book')->all)[1]->title
+    SELECT me.id, me.title, me.rating FROM books me:
+    0  'TCP/IP Illustrated, Volume 1'
+
+This uses the Perl debugger C<x> command to display the title of a book.
+
+Next we inspect the C<books> element of the Catalyst C<stash> (the C<4>
+argument to the C<x> command limits the depth of the dump to 4 levels):
+
+      DB<3> x 4 $c->stash->{books}
+    0  ARRAY(0xa8f3b7c)
+       0  MyApp::Model::MyAppDB::Book=HASH(0xb8e702c)
+          '_column_data' => HASH(0xb8e5e2c)
+             'id' => 1
+             'rating' => 5
+             'title' => 'CCSP SNRS Exam Certification Guide'
+          '_in_storage' => 1
+    <lines removed for brevity>
+
+Then enter the C<c> command to continue processing until the next
+breakpoint is hit (or the application exits):
+
+      DB<4> c
+    SELECT author.id, author.first_name, author.last_name FROM ...
+
+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
+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.
+
+
+=head1 DEBUGGING MODULES FROM CPAN
+
+Although the techniques discussed above work well for code you are 
+writing, what if you want to use print/log/warn messages or set 
+breakpoints in code that you have installed from CPAN (or in module that 
+ship with Perl)?  One helpful approach is to place a copy of the module 
+inside the C<lib> directory of your Catalyst project.  When Catalyst 
+loads, it will load from inside your C<lib> directory first, only 
+turning to the global modules if a local copy cannot be found.  You can 
+then make modifications such as adding a C<$DB::single=1> to the local 
+copy of the module without risking the copy in the original location. 
+This can also be a great way to "locally override" bugs in modules while 
+you wait for a fix on CPAN.
+
+
+Matt Trout has suggested the following shortcut to create a local
+copy of an installed module:
+
+    mkdir -p lib/Module; cp `perldoc -l Module::Name` lib/Module/
+
+For example, you could make a copy of 
+L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>
+with the following command:
+
+    mkdir -p lib/Catalyst/Plugin; cp \
+        `perldoc -l Catalyst::Plugin::Authentication` lib/Catalyst/Plugin
+
+B<Note:> Matt has also suggested the following tips for Perl 
+debugging:
+
+=over 4
+
+=item * 
+
+Check the version of an installed module:
+
+    perl -MModule::Name -e 'print $Module::Name::VERSION;'
+
+For example:
+
+    $ perl -MCatalyst::Plugin::Authentication -e \
+        'print $Catalyst::Plugin::Authentication::VERSION;'
+    0.07
+
+=item * 
+
+Check if a modules contains a given method:
+
+    perl -MModule::Name -e 'print Module::Name->can("method");'
+
+For example:
+
+    $ perl -MCatalyst::Plugin::Authentication -e \
+        'print Catalyst::Plugin::Authentication->can("prepare");'
+    CODE(0x9c8db2c)
+
+If the method exists, the Perl C<can> method returns a coderef.
+Otherwise, it returns undef and nothing will be printed.
+
+=back
+
+
+=head1 AUTHOR
+
+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-Runtime/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
diff --git a/lib/Catalyst/Manual/Tutorial/Tutorial/Intro.pod b/lib/Catalyst/Manual/Tutorial/Tutorial/Intro.pod
new file mode 100644 (file)
index 0000000..524bcf2
--- /dev/null
@@ -0,0 +1,353 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::Intro - Catalyst Tutorial - Part 1: Introduction
+
+
+=head1 OVERVIEW
+
+This is B<Part 1 of 9> of the Catalyst Tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+B<Introduction>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
+
+=item 3
+
+L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
+
+=item 4
+
+L<Authentication|Catalyst::Manual::Tutorial::Authentication>
+
+=item 5
+
+L<Authorization|Catalyst::Manual::Tutorial::Authorization>
+
+=item 6
+
+L<Debugging|Catalyst::Manual::Tutorial::Debugging>
+
+=item 7
+
+L<Testing|Catalyst::Manual::Tutorial::Testing>
+
+=item 8
+
+L<Advanced CRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
+
+=item 9
+
+L<Appendices|Catalyst::Manual::Tutorial::Appendices>
+
+=back
+
+=head1 DESCRIPTION
+
+This tutorial provides a multipart introduction to the Catalyst web
+framework. It seeks to provide a rapid overview of many of its most
+commonly used features. The focus is on the real-world best practices
+required in the construction of nearly all Catalyst applications.
+
+Although the primary target of the tutorial is users new to the Catalyst
+framework, experienced users may wish to review specific sections (for
+example, how to use DBIC for their model classes or how to add
+authentication and authorization to an existing application).
+
+You can obtain the code for all the tutorial examples from the
+catalyst subversion repository by issuing the command:
+
+    svn co http://dev.catalyst.perl.org/repos/Catalyst/tags/examples/Tutorial/MyApp/5.7/ CatalystTutorial
+
+This will download the current code for each tutorial chapter in the
+CatalystTutorial directory.  Each example application directory has
+the same name as the tutorial chapter.
+
+Additionally, if you're reading this manual online, you can download
+the manual, the example program, and all the necessary dependencies to
+your local machine by installing the C<Task::Catalyst::Tutorial>
+distribution from CPAN:
+
+     cpan Task::Catalyst::Tutorial
+
+This will also test to make sure the dependencies are working.  If you
+have trouble installing these, please ask for help on the #catalyst
+IRC channel, or the Catalyst mailing list.
+
+Subjects covered include:
+
+=over 4
+
+=item * 
+
+A simple application that lists and adds books.
+
+=item *
+
+The use of L<DBIx::Class|DBIx::Class> (DBIC) for the model.
+
+=item * 
+
+How to write CRUD (Create, Read, Update, and Delete) operations in
+Catalyst.
+
+=item *
+
+Authentication ("auth").
+
+=item * 
+
+Role-based authorization ("authz").
+
+=item * 
+
+Attempts to provide an example showing current (5.7XXX) Catalyst
+practices. For example, the use of 
+L<Catalyst::Action::RenderView|Catalyst::Action::RenderView>,
+DBIC, L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader> 
+with C<myapp.yml>, the use of C<lib/MyApp/Controller/Root.pm> 
+vs. C<lib/MyApp.pm>, etc.
+
+=item * 
+
+The use of Template Toolkit (TT) and the
+L<Catalyst::Helper::View::TTSite|Catalyst::Helper::View::TTSite> 
+view helper.
+
+=item * 
+
+Useful techniques for troubleshooting and debugging Catalyst
+applications.
+
+=item * 
+
+The use of SQLite as a database (with code also provided for MySQL and
+PostgreSQL).
+
+=item * 
+
+The use of L<HTML::Widget|HTML::Widget> for automated form processing 
+and validation.
+
+=back
+
+This tutorial makes the learning process its main priority.  For
+example, the level of comments in the code found here would likely be
+considered excessive in a "normal project".  Because of their contextual
+value, this tutorial will generally favor inline comments over a
+separate discussion in the text.  It also deliberately tries to
+demonstrate multiple approaches to various features (in general, you
+should try to be as consistent as possible with your own production
+code).
+
+Furthermore, this tutorial tries to minimize the number of controllers,
+models, TT templates, and database tables.  Although this does result in
+things being a bit contrived at times, the concepts should be applicable
+to more complex environments.  More complete and complicated example
+applications can be found in the C<examples> area of the Catalyst
+Subversion repository at
+L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/>.
+
+B<Note:> There are a variety of other introductory materials available
+through the Catalyst web site and at
+L<http://dev.catalyst.perl.org/wiki/UserIntroductions> and
+L<http://dev.catalyst.perl.org/>.
+
+=head1 VERSIONS AND CONVENTIONS USED IN THIS TUTORIAL
+
+This tutorial was built using the following resources. Please note that
+you may need to make adjustments for different environments and
+versions:
+
+=over 4
+
+=item * 
+
+OS = CentOS 4 Linux (RHEL 4)
+
+=item * 
+
+Catalyst v5.6902
+
+=item * 
+
+DBIx::Class v0.06003
+
+=item * 
+
+Catalyst Plugins
+
+The plugins used in this tutorial all have sufficiently stable APIs that
+you shouldn't need to worry about versions. However, there could be
+cases where the tutorial is affected by what version of plugins you
+use. This tutorial has been tested against the following set of plugins:
+
+=over 4
+
+=item * 
+
+Catalyst::Plugin::Authentication -- 0.09
+
+=item *
+
+Catalyst::Plugin::Authentication::Store::DBIC -- 0.07
+
+=item *
+
+Catalyst::Plugin::Authorization::ACL -- 0.08
+
+=item *
+
+Catalyst::Plugin::Authorization::Roles -- 0.04
+
+=item *
+
+Catalyst::Plugin::ConfigLoader -- 0.13
+
+=item *
+
+Catalyst::Plugin::HTML::Widget -- 1.1
+
+=item *
+
+Catalyst::Plugin::Session -- 0.12
+
+=item *
+
+Catalyst::Plugin::Session::State::Cookie -- 0.05
+
+=item *
+
+Catalyst::Plugin::Session::Store::FastMmap -- 0.02
+
+=item *
+
+Catalyst::Plugin::StackTrace -- 0.06
+
+=item *
+
+Catalyst::Plugin::Static::Simple -- 0.14
+
+=back
+
+=item * 
+
+Since the web browser is being used on the same box where Perl and the
+Catalyst development server is running, the URL of
+C<http://localhost:3000> will be used (the Catalyst development server
+defaults to port 3000).  If you are running Perl on a different box than
+where your web browser is located (or using a different port number via
+the C<-p> I<port_number> option to the development server), then you
+will need to update the URL you use accordingly.
+
+=item * 
+
+Depending on the web browser you are using, you might need to hit
+C<Shift+Reload> to pull a fresh page when testing your application at
+various points.  Also, the C<-k> keepalive option to the development
+server can be necessary with some browsers (especially Internet
+Explorer).
+
+=back
+
+=head1 CATALYST INSTALLATION
+
+Unfortunately, one of the most daunting tasks faced by newcomers to
+Catalyst is getting it installed.  Although a compelling strength of
+Catalyst is that it can easily make use of many of the modules in the
+vast repository that is CPAN, this can result in initial installations
+that are both time consuming and frustrating.  However, there are a
+growing number of methods that can dramatically ease this undertaking.
+Of these, the following are likely to be applicable to the largest
+number of potential new users:
+
+=over 4
+
+=item * 
+
+Matt Trout's C<cat-install>
+
+Available at L<http://www.shadowcatsystems.co.uk/static/cat-install>,
+C<cat-install> can be a quick and painless way to get Catalyst up and
+running.  Just download the script from the link above and type C<perl
+cat-install>.
+
+=item * 
+
+Chris Laco's CatInABox
+
+Download the tarball from
+L<http://handelframework.com/downloads/CatInABox.tar.gz> and unpack it
+on your machine.  Depending on your OS platform, either run C<start.bat>
+or C<start.sh>.
+
+=item * 
+
+Pre-Built VMWare Images
+
+Under the VMWare community program, work is ongoing to develop a number
+of VMWare images where an entire Catalyst development environment has
+already been installed, complete with database engines and a full
+complement of Catalyst plugins.
+
+=back
+
+For additional information and recommendations on Catalyst installation,
+please refer to 
+L<Catalyst::Manual::Installation|Catalyst::Manual::Installation>.
+
+B<NOTE:> Step-by-step instructions to replicate the environment on
+which this tutorial was developed can be found at
+L<Catalyst::Manual::Installation::CentOS4|Catalyst::Manual::Installation::CentOS4>. 
+Using these instructions, you should be able to build a complete CentOS
+4.X server with Catalyst and all the plugins required to run this
+tutorial.
+
+=head1 DATABASES
+
+This tutorial will primarily focus on SQLite because of its simplicity
+of installation and use; however, modifications in the script required
+to support MySQL and PostgreSQL will be presented in Appendix 2.
+
+B<Note:> One of the advantages of the MVC design patterns is that
+applications become much more database independent.  As such, you will
+notice that only the C<.sql> files used to initialize the database
+change between database systems: the Catalyst code generally remains the
+same.
+
+=head1 WHERE TO GET WORKING CODE
+
+Each part of the tutorial has complete code available in the main
+Catalyst Subversion repository (see the note at the beginning of each
+part for the appropriate svn command to use).  Additionally, the final
+code is available as a ready-to-run tarball at
+L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial/Final_Tarball/MyApp.tgz>.
+
+B<NOTE:> You can run the test cases for the final code with the following 
+commands:
+
+    wget http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial/Final_Tarball/MyApp.tgz
+    tar zxvf MyApp.tgz
+    cd MyApp
+    CATALYST_DEBUG=0 prove --lib lib  t
+
+
+=head1 AUTHOR
+
+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-Runtime/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
+
+
diff --git a/lib/Catalyst/Manual/Tutorial/Tutorial/Testing.pod b/lib/Catalyst/Manual/Tutorial/Tutorial/Testing.pod
new file mode 100644 (file)
index 0000000..f3b2b64
--- /dev/null
@@ -0,0 +1,351 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::Testing - Catalyst Tutorial - Part 7: Testing
+
+
+=head1 OVERVIEW
+
+This is B<Part 7 of 9> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
+
+=item 3
+
+L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>
+
+=item 4
+
+L<Authentication|Catalyst::Manual::Tutorial::Authentication>
+
+=item 5
+
+L<Authorization|Catalyst::Manual::Tutorial::Authorization>
+
+=item 6
+
+L<Debugging|Catalyst::Manual::Tutorial::Debugging>
+
+=item 7
+
+B<Testing>
+
+=item 8
+
+L<AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
+
+=item 9
+
+L<Appendices|Catalyst::Manual::Tutorial::Appendices>
+
+=back
+
+=head1 DESCRIPTION
+
+You may have noticed that the Catalyst Helper scripts automatically
+create basic C<.t> test scripts under the C<t> directory.  This part of
+the tutorial briefly looks at how these tests can be used to not only
+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.
+
+You can checkout the source code for this example from the catalyst
+subversion repository as per the instructions in
+L<Catalyst::Manual::Tutorial::Intro>
+
+=head1 RUNNING THE "CANNED" CATALYST TESTS
+
+There are a variety of ways to run Catalyst and Perl tests (for example,
+C<perl Makefile.PL> and C<make test>), but one of the easiest is with the
+C<prove> command.  For example, to run all of the tests in the C<t>
+directory, enter:
+
+    $ prove --lib lib t
+
+The redirection used by the Authentication plugins will cause the
+default C<t/01app.t> to fail.  You can fix this by changing the line in
+C<t/01app.t> that read:
+
+    ok( request('/')->is_success, 'Request should succeed' );
+
+to:
+
+    ok( request('/login')->is_success, 'Request should succeed' );
+
+So that a redirect is not necessary.  Also, the C<t/controller_Books.t>
+and C<t/controller_Logout.t> default test cases will fail because of the
+authorization.  You can delete these two files to prevent false error
+messages:
+
+    $ rm t/controller_Books.t
+    $ rm t/controller_Logout.t
+
+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
+command, you will get all of the usual development server debug output,
+something most people prefer to disable while running tests cases.
+Although you can edit the C<lib/MyApp.pm> to comment out the C<-Debug>
+plugin, it's generally easier to simply set the C<CATALYST_DEBUG=0>
+environment variable.  For example:
+
+    $ CATALYST_DEBUG=0 prove --lib lib t
+
+During the C<t/02pod> and C<t/03podcoverage> tests, you might notice the
+C<all skipped: set TEST_POD to enable this test> warning message.  To
+execute the Pod-related tests, add C<TEST_POD=1> to the C<prove>
+command:
+
+    $ CATALYST_DEBUG=0 TEST_POD=1 prove --lib lib t
+
+If you omitted the Pod comments from any of the methods that were
+inserted, you might have to go back and fix them to get these tests to
+pass. :-)
+
+Another useful option is the C<verbose> (C<-v>) option to C<prove>.  It
+prints the name of each test case as it is being run:
+
+    $ CATALYST_DEBUG=0 TEST_POD=1 prove --lib lib -v t
+
+=head1 RUNNING A SINGLE TEST
+
+You can also run a single script by appending its name to the C<prove>
+command. For example:
+
+    $ CATALYST_DEBUG=0 prove --lib lib t/01app.t
+
+Note that you can also run tests directly from Perl without C<prove>.
+For example:
+
+    $ CATALYST_DEBUG=0 perl -Ilib t/01app.t
+
+=head1 ADDING YOUR OWN TEST SCRIPT
+
+Although the Catalyst helper scripts provide a basic level of checks
+"for free," testing can become significantly more helpful when you write
+your own script to exercise the various parts of your application.  The
+L<Test::WWW::Mechanize::Catalyst|Test::WWW::Mechanize::Catalyst> module 
+is very popular for writing these sorts of test cases.  This module 
+extends L<Test::WWW::Mechanize|Test::WWW::Mechanize> (and therefore 
+L<WWW::Mechanize|WWW::Mechanize>) to allow you to automate the action of
+a user "clicking around" inside your application.  It gives you all the
+benefits of testing on a live system without the messiness of having to
+use an actual web server, and a real person to do the clicking.
+
+To create a sample test case, open the C<t/live_app01.t> file in your
+editor and enter the following:
+
+    #!/usr/bin/perl
+    
+    use strict;
+    use warnings;
+    
+    # Load testing framework and use 'no_plan' to dynamically pick up
+    # all tests. Better to replace "'no_plan'" with "tests => 30" so it
+    # knows exactly how many tests need to be run (and will tell you if
+    # not), but 'no_plan' is nice for quick & dirty tests
+    
+    use Test::More 'no_plan';
+    
+    # Need to specify the name of your app as arg on next line
+    # Can also do:
+    #   use Test::WWW::Mechanize::Catalyst "MyApp";
+    
+    use ok "Test::WWW::Mechanize::Catalyst" => "MyApp";
+        
+    # Create two 'user agents' to simulate two different users ('test01' & 'test02')
+    my $ua1 = Test::WWW::Mechanize::Catalyst->new;
+    my $ua2 = Test::WWW::Mechanize::Catalyst->new;
+    
+    # Use a simplified for loop to do tests that are common to both users
+    # Use get_ok() to make sure we can hit the base URL
+    # Second arg = optional description of test (will be displayed for failed tests)
+    # Note that in test scripts you send everything to 'http://localhost'
+    $_->get_ok("http://localhost/", "Check redirect of base URL") for $ua1, $ua2;
+    # Use title_is() to check the contents of the <title>...</title> tags
+    $_->title_is("Login", "Check for login title") for $ua1, $ua2;
+    # Use content_contains() to match on text in the html body
+    $_->content_contains("You need to log in to use this application",
+        "Check we are NOT logged in") for $ua1, $ua2;
+    
+    # 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',
+        });
+    
+    # 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;
+    $_->title_is("Login", "Check for login page") for $ua1, $ua2;
+    $_->content_contains("Please Note: You are already logged in as ",
+        "Check we ARE logged in" ) for $ua1, $ua2;
+    
+    # 'Click' the 'Logout' link (see also 'text_regex' and 'url_regex' options)
+    $_->follow_link_ok({n => 1}, "Logout via first link on page") for $ua1, $ua2;
+    $_->title_is("Login", "Check for login title") for $ua1, $ua2;
+    $_->content_contains("You need to log in to use this application",
+        "Check we are NOT logged in") for $ua1, $ua2;
+    
+    # Log back in
+    $ua1->get_ok("http://localhost/login?username=test01&password=mypass", "Login 'test01'");
+    $ua2->get_ok("http://localhost/login?username=test02&password=mypass", "Login 'test02'");
+    # Should be at the Book List page... do some checks to confirm
+    $_->title_is("Book List", "Check for book list title") for $ua1, $ua2;
+    
+    $ua1->get_ok("http://localhost/books/list", "'test01' book list");
+    $ua1->get_ok("http://localhost/login", "Login Page");
+    $ua1->get_ok("http://localhost/books/list", "'test01' book list");
+    
+    $_->content_contains("Book List", "Check for book list title") for $ua1, $ua2;
+    # Make sure the appropriate logout buttons are displayed
+    $_->content_contains("/logout\">Logout</a>",
+        "Both users should have a 'User Logout'") for $ua1, $ua2;
+    $ua1->content_contains("/books/form_create\">Create</a>",
+        "Only 'test01' should have a create link");
+    
+    $ua1->get_ok("http://localhost/books/list", "View book list as 'test01'");
+    
+    # User 'test01' should be able to create a book with the "formless create" URL
+    $ua1->get_ok("http://localhost/books/url_create/TestTitle/2/4",
+        "'test01' formless create");
+    $ua1->title_is("Book Created", "Book created title");
+    $ua1->content_contains("Added book 'TestTitle'", "Check title added OK");
+    $ua1->content_contains("by 'Stevens'", "Check author added OK");
+    $ua1->content_contains("with a rating of 2.", "Check rating added");
+    # Try a regular expression to combine the previous 3 checks & account for whitespace
+    $ua1->content_like(qr/Added book 'TestTitle'\s+by 'Stevens'\s+with a rating of 2./, "Regex check");
+    
+    # Make sure the new book shows in the list
+    $ua1->get_ok("http://localhost/books/list", "'test01' book list");
+    $ua1->title_is("Book List", "Check logged in and at book list");
+    $ua1->content_contains("Book List", "Book List page test");
+    $ua1->content_contains("TestTitle", "Look for 'TestTitle'");
+    
+    # Make sure the new book can be deleted
+    # Get all the Delete links on the list page
+    my @delLinks = $ua1->find_all_links(text => 'Delete');
+    # Use the final link to delete the last book
+    $ua1->get_ok($delLinks[$#delLinks]->url, 'Delete last book');
+    # Check that delete worked
+    $ua1->content_contains("Book List", "Book List page test");
+    $ua1->content_contains("Book deleted", "Book was deleted");
+    
+    # User 'test02' should not be able to add a book
+    $ua2->get_ok("http://localhost/books/url_create/TestTitle2/2/5", "'test02' add");
+    $ua2->content_contains("Unauthorized!", "Check 'test02' cannot add");
+
+The C<live_app.t> test cases uses copious comments to explain each step
+of the process.  In addition to the techniques shown here, there are a
+variety of other methods available in 
+L<Test::WWW::Mechanize::Catalyst|Test::WWW::Mechanize::Catalyst> (for 
+example, regex-based matching). Consult the documentation for more
+detail.
+
+B<TIP>: For I<unit tests> vs. the "full application tests" approach used
+by L<Test::WWW::Mechanize::Catalyst|Test::WWW::Mechanize::Catalyst>, see 
+L<Catalyst::Test|Catalyst::Test>.
+
+B<Note:> The test script does not test the C<form_create> and
+C<form_create_do> actions.  That is left as an exercise for the reader
+(you should be able to complete that logic using the existing code as a
+template).
+
+To run the new test script, use a command such as:
+
+    $ CATALYST_DEBUG=0 prove --lib lib -v t/live_app01.t
+
+or
+
+    $ DBIC_TRACE=0 CATALYST_DEBUG=0 prove --lib lib -v t/live_app01.t
+
+Experiment with the C<DBIC_TRACE>, C<CATALYST_DEBUG>
+and C<-v> settings.  If you find that there are errors, use the
+techniques discussed in the "Catalyst Debugging" section (Part 6) to
+isolate and fix any problems.
+
+If you want to run the test case under the Perl interactive debugger,
+try a command such as:
+
+    $ DBIC_TRACE=0 CATALYST_DEBUG=0 perl -d -Ilib t/live_app01.t
+
+Note that although this tutorial uses a single custom test case for
+simplicity, you may wish to break your tests into different files for
+better organization.
+
+B<TIP:> If you have a test case that fails, you will receive an error
+similar to the following:
+
+    #   Failed test 'Check we are NOT logged in'
+    #   in t/live_app01.t at line 31.
+    #     searched: "\x{0a}<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Tran"...
+    #   can't find: "You need to log in to use this application."
+
+Unfortunately, this only shows us the first 50 characters of the HTML
+returned by the request -- not enough to determine where the problem
+lies.  A simple technique that can be used in such situations is to 
+temporarily insert a line similar to the following right after the 
+failed test:
+
+    warn $ua1->content;
+
+This will cause the full HTML returned by the request to be displayed.
+
+
+=head1 SUPPORTING BOTH PRODUCTION AND TEST DATABASES
+
+You may wish to leverage the techniques discussed in this tutorial to
+maintain both a "production database" for your live application and a
+"testing database" for your test cases.  One advantage to
+L<Test::WWW::Mechanize::Catalyst|Test::WWW::Mechanize::Catalyst> is that
+it runs your full application; however, this can complicate things when
+you want to support multiple databases.  One solution is to allow the
+database specification to be overridden with an environment variable.
+For example, open C<lib/MyApp/Model/MyAppDB.pm> in your editor and
+change the C<__PACKAGE__-E<gt>config(...> declaration to resemble:
+
+    my $dsn = $ENV{MYAPP_DSN} ||= 'dbi:SQLite:myapp.db';
+    __PACKAGE__->config(
+        schema_class => 'MyAppDB',
+        connect_info => [
+            $dsn,
+            '',
+            '',
+            { AutoCommit => 1 },
+    
+        ],
+    );
+
+Then, when you run your test case, you can use commands such as:
+
+    $ cp myapp.db myappTEST.db
+    $ CATALYST_DEBUG=0 MYAPP_DSN="dbi:SQLite:myappTEST.db" prove --lib lib -v t/live_app01.t
+
+This will modify the DSN only while the test case is running.  If you
+launch your normal application without the C<MYAPP_DSN> environment
+variable defined, it will default to the same C<dbi:SQLite:myapp.db> as
+before.
+
+
+=head1 AUTHOR
+
+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-Runtime/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
+