+++ /dev/null
-=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/>).
+++ /dev/null
-=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/>).
+++ /dev/null
-=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/>).
+++ /dev/null
-=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/>).
-
+++ /dev/null
-=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/>).
-
+++ /dev/null
-=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>, that 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/>).
-
+++ /dev/null
-=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/>).
+++ /dev/null
-=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.
-
-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/>).
-
-
+++ /dev/null
-=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/>).
-