From: Jonathan Rockway Date: Fri, 27 Oct 2006 16:53:28 +0000 (+0000) Subject: Moving tutorial POD here X-Git-Tag: v5.8005~368 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Manual.git;a=commitdiff_plain;h=d442cc9fbdf45a28fe02f13552f44e3e2f7ec22e Moving tutorial POD here --- diff --git a/lib/Catalyst/Manual.pm b/lib/Catalyst/Manual.pm new file mode 100644 index 0000000..01fa2a4 --- /dev/null +++ b/lib/Catalyst/Manual.pm @@ -0,0 +1,25 @@ +#!perl +# Manual.pm +# Copyright (c) 2006 Jonathan Rockway + +package Catalyst::Manual; + +=head1 NAME + +Catalyst::Manual - pacakge holding the Catalyst developer's manual and tutorial + +=head1 SYNOPSIS + + perldoc Catalyst::Manual::Intro + perldoc Catalyst::Tutorial + +=head1 SEE ALSO + +Get L to install +all the dependencies you need to follow along with the Tutorial. + +=cut + +our $VERSION = '0.01'; + +1; diff --git a/lib/Catalyst/Manual/Tutorial/Tutorial/AdvancedCRUD.pod b/lib/Catalyst/Manual/Tutorial/Tutorial/AdvancedCRUD.pod new file mode 100644 index 0000000..77b81cd --- /dev/null +++ b/lib/Catalyst/Manual/Tutorial/Tutorial/AdvancedCRUD.pod @@ -0,0 +1,764 @@ +=head1 NAME + +Catalyst::Manual::Tutorial::AdvancedCRUD - Catalyst Tutorial - Part 8: Advanced CRUD + + +=head1 OVERVIEW + +This is B for the Catalyst tutorial. + +L + +=over 4 + +=item 1 + +L + +=item 2 + +L + +=item 3 + +L + +=item 4 + +L + +=item 5 + +L + +=item 6 + +L + +=item 7 + +L + +=item 8 + +B + +=item 9 + +L + +=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 +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 and +L. + +Here, we will make use of the L 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 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 Part 8 of the tutorial is optional. Users who do not wish to +use L may skip this part. + +You can checkout the source code for this example from the catalyst subversion repository as per the instructions in L + +=head1 C FORM CREATION + +This section looks at how L can be used to +add additional functionality to the manually created form from Part 3. + +=head2 Add the C Plugin + +Open C 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 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 +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 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 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. Doing +so allows us to have the same form submit the data to different actions +(e.g., C for a create operation but C to +update an existing book object). + +B If you receive an error about Catalyst not being able to find +the template C, please verify that you followed the +instructions in the final section of +L where +you returned to a manually-specified template. You can either use +C/C B default template names, but the two cannot +be used together. + + +=head2 Update the CSS + +Edit C 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 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 in your editor and enter the following: + + [% META title = 'Create/Update Book' %] + + [% widget_result.as_xml %] + +

Return to book list

+ +=head2 Add Links for Create and Update via C + +Open C in your editor and add the following to +the bottom of the existing file: + +

+ HTML::Widget: + Create +

+ + +=head2 Test The Create Form + +Press C to kill the previous server instance (if it's still +running) and restart it: + + $ script/myapp_server.pl + +Login as C. Once at the Book List page, click the HTML::Widget +"Create" link to display for form produced by C. 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 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 itself is not +performing any validation. + +=head1 C VALIDATION AND FILTERING + +Although the use of L 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 in your editor and update the +C 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 + + + Password: + + + + + + + + + +=head2 Add Valid User Check + +We need something that provides enforcement for the authentication +mechanism -- a I mechanism that prevents users who have not +passed authentication from reaching any pages except the login page. +This is generally done via an C action/method (prior to Catalyst +v5.66, this sort of thing would go in C, but starting in +v5.66, the preferred location is C). + +Edit the existing C 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 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 Catalyst provides a number of different types of actions, such +as C, C, and C. You should refer to +L for a more detailed explanation, but the +following bullet points provide a quick introduction: + +=over 4 + +=item * + +The majority of application use C actions for items that respond +to user requests and C actions for those that do not directly +respond to user input. + +=item * + +There are five types of C actions: C, C, +C, C, and C. + +=item * + +With C, C, C, C private actions, only the +most specific action of each type will be called. For example, if you +define a C action in your controller it will I a +C action in your application/root controller -- I the +action in your controller will be called. + +=item * + +Unlike the other actions where only a single method is called for each +request, I auto action along the chain of namespaces will be +called. Each C action will be called I. + +=back + +By placing the authentication enforcement code inside the C method +of C (or C), it will be +called for I 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 in your editor and add the following +lines to the bottom of the file: + +

+ [% + # 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 logout 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 to kill the previous server instance (if it's still +running) and restart it: + + $ script/myapp_server.pl + +B: If you happen to be using Internet Explorer, you may +need to use the command C