3 Catalyst::Manual::Tutorial::AdvancedCRUD - Catalyst Tutorial - Part 8: Advanced CRUD
7 This is B<Part 8 of 9> for the Catalyst tutorial.
9 L<Tutorial Overview|Catalyst::Manual::Tutorial>
15 L<Introduction|Catalyst::Manual::Tutorial::Intro>
19 L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
23 L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>
27 L<Authentication|Catalyst::Manual::Tutorial::Authentication>
31 L<Authorization|Catalyst::Manual::Tutorial::Authorization>
35 L<Debugging|Catalyst::Manual::Tutorial::Debugging>
39 L<Testing|Catalyst::Manual::Tutorial::Testing>
47 L<Appendices|Catalyst::Manual::Tutorial::Appendices>
53 This part of the tutorial explores more advanced functionality for
54 Create, Read, Update, and Delete (CRUD) than we saw in Part 3. In
55 particular, it looks at a number of techniques that can be useful for
56 the Update portion of CRUD, such as automated form generation,
57 validation of user-entered data, and automated transfer of data between
58 forms and model objects.
60 In keeping with the Catalyst (and Perl) spirit of flexibility, there are
61 many different ways to approach advanced CRUD operations in a Catalyst
62 environment. One alternative is to use
63 L<Catalyst::Helper::Controller::Scaffold|Catalyst::Helper::Controller::Scaffold>
64 to instantly construct a set of Controller methods and templates for
65 basic CRUD operations. Although a popular subject in Quicktime
66 movies that serve as promotional material for various frameworks,
67 real-world applications generally require more control. Other
68 options include L<Data::FormValidator|Data::FormValidator> and
69 L<HTML::FillInForm|HTML::FillInForm>.
71 Here, we will make use of the L<HTML::Widget|HTML::Widget> to not only
72 ease form creation, but to also provide validation of the submitted
73 data. The approached used by the part of the tutorial is to slowly
74 incorporate additional L<HTML::Widget|HTML::Widget> functionality in a
75 step-wise fashion (we start with fairly simple form creation and then
76 move on to more complex and "magical" features such as validation and
77 auto-population/auto-saving).
79 B<Note:> Part 8 of the tutorial is optional. Users who do not wish to
80 use L<HTML::Widget|HTML::Widget> may skip this part.
82 B<TIP>: Note that all of the code for this part of the tutorial can be
83 pulled from the Catalyst Subversion repository in one step with the
86 svn checkout http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial@###
87 IMPORTANT: Does not work yet. Will be completed for final version.
89 =head1 C<HTML::WIDGET> FORM CREATION
91 This section looks at how L<HTML::Widget|HTML::Widget> can be used to
92 add additional functionality to the manually created form from Part 3.
94 =head2 Add the C<HTML::Widget> Plugin
96 Open C<lib/MyApp.pm> in your editor and add the following to the list of
97 plugins (be sure to leave the existing plugins enabled):
101 =head2 Add a Form Creation Helper Method
103 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
106 =head2 make_book_widget
108 Build an HTML::Widget form for book creation and updates
112 sub make_book_widget {
115 # Create an HTML::Widget to build the form
116 my $w = $c->widget('book_form')->method('post');
119 my @authorObjs = $c->model("MyAppDB::Author")->all();
120 my @authors = map {$_->id => $_->last_name }
121 sort {$a->last_name cmp $b->last_name} @authorObjs;
123 # Create the form feilds
124 $w->element('Textfield', 'title' )->label('Title')->size(60);
125 $w->element('Textfield', 'rating' )->label('Rating')->size(1);
126 $w->element('Select', 'authors')->label('Authors')
128 $w->element('Submit', 'submit' )->value('submit');
134 This method provides a central location that builds an HTML::Widget-
135 based form with the appropriate fields. The "Get Authors" code uses
136 DBIC to retrieve a list of model objects and then uses C<map> to create
137 a hash where the hash keys are the database primary keys from the
138 authors table and the associated values are the last names of the
141 =head2 Add Actions to Display and Save the Form
143 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
148 Build an HTML::Widget form for book creation and updates
152 sub hw_create : Local {
155 # Create the widget and set the action for the form
156 my $w = $self->make_book_widget($c);
157 $w->action($c->uri_for('hw_create_do'));
159 # Write form to stash variable for use in template
160 $c->stash->{widget_result} = $w->result;
163 $c->stash->{template} = 'books/hw_form.tt2';
169 Build an HTML::Widget form for book creation and updates
173 sub hw_create_do : Local {
176 # Retrieve the data from the form
177 my $title = $c->request->params->{title};
178 my $rating = $c->request->params->{rating};
179 my $authors = $c->request->params->{authors};
181 # Call create() on the book model object. Pass the table
182 # columns/field values we want to set as hash values
183 my $book = $c->model('MyAppDB::Book')->create({
188 # Add a record to the join table for this book, mapping to
190 $book->add_to_book_authors({author_id => $authors});
192 # Set a status message for the user
193 $c->stash->{status_msg} = 'Book created';
195 # Use 'hw_create' to redisplay the form
196 $c->detach('hw_create');
199 Note how we use C<make_book_widget> to build the core parts of the form
200 in one location, but we set the action (the URL the form is sent to when
201 the user clicks the 'Submit' button) separately in C<hw_create>. Doing
202 so allows us to have the same form submit the data to different actions
203 (e.g., C<hw_create_do> for a create operation but C<hw_update_do> to
204 update an existing book object).
206 =head2 Update the CSS
208 Edit C<root/src/ttsite.css> and add the following lines to the bottom of
229 color: [% site.col.error %];
232 These changes will display form elements vertically and also show error
233 messages in red. Note that we are pulling the color scheme settings
234 from the C<root/lib/config/col> file that was created by the TTSite
235 helper. This allows us to change the color used by various error styles
236 in the CSS from a single location.
238 =head2 Create a Template Page To Display The Form
240 Open C<root/src/books/hw_form.tt2> in your editor and enter the following:
242 [% META title = 'Create/Update Book' %]
244 [% widget_result.as_xml %]
246 <p><a href="[% Catalyst.uri_for('list') %]">Return to book list</a></p>
248 =head2 Add Links for Create and Update via C<HTML::Widget>
250 Open C<root/src/books/list.tt2> in your editor and add the following to
251 the bottom of the existing file:
255 <a href="[% Catalyst.uri_for('hw_create') %]">Create</a>
256 <a href="[% Catalyst.uri_for('hw_update') %]">Update</a>
259 =head2 Test The <HTML::Widget> Create Form
261 Press C<Ctrl-C> to kill the previous server instance (if it's still
262 running) and restart it:
264 $ script/myapp_server.pl
266 Login as C<test01>. Once at the Book List page, click the HTML::Widget
267 "Create" link to display for form produced by C<make_book_widget>. Fill
268 out the form with the following values: Title = "Internetworking with
269 TCP/IP Vol. II", Rating = "4", and Author = "Comer". Click Submit, and
270 you will be returned to the Create/Update Book page with a "Book
271 created" status message displayed. Click "Return to book list" to view
272 the newly created book on the main list.
274 Also note that this implementation allows you to can create books with
275 bogus information. Although we have constrained the authors with the
276 drop-down list, there are no restrictions on items such as the length of
277 the title (for example, you can create a one-letter title) and value for
278 the rating (you can use any number you want, and even non-numeric values
279 with SQLite). The next section will address this concern.
281 B<Note:> Depending on the database you are using and how you established
282 the columns in your tables, the database could obviously provide various
283 levels of "type enforcement" on your data. The key point being made in
284 the previous paragraph is that the I<web application> itself is not
285 performing any validation.
287 =head1 C<HTML::WIDGET> VALIDATION AND FILTERING
289 Although the use of L<HTML::Widget|HTML::Widget> in the previous section
290 did provide an automated mechanism to build the form, the real power of
291 this module stems from functionality that can automatically validate and
292 filter the user input. Validation uses constraints to be sure that
293 users input appropriate data (for example, that the email field of a
294 form contains a valid email address). Filtering can be used to remove
295 extraneous whitespace from fields or to escape meta-characters in user
298 =head2 Add Constraints and Filters to the Widget Creation Method
300 Open C<lib/MyApp/Controller/Books.pm> in your editor and update the
301 C<make_book_widget> method to match the following (new sections have
302 been marked with a C<*** NEW:> comment):
304 sub make_book_widget {
307 # Create an HTML::Widget to build the form
308 my $w = $c->widget('book_form')->method('post');
311 my @authorObjs = $c->model("MyAppDB::Author")->all();
312 my @authors = map {$_->id => $_->last_name }
313 sort {$a->last_name cmp $b->last_name} @authorObjs;
315 # Create the form feilds
316 $w->element('Textfield', 'title' )->label('Title')->size(60);
317 $w->element('Textfield', 'rating' )->label('Rating')->size(1);
318 # ***NEW: Convert to multi-select list
319 $w->element('Select', 'authors')->label('Authors')
320 ->options(@authors)->multiple(1)->size(3);
321 $w->element('Submit', 'submit' )->value('submit');
323 # ***NEW: Set constraints
324 $w->constraint(All => qw/title rating authors/)
325 ->message('Required. ');
326 $w->constraint(Integer => qw/rating/)
327 ->message('Must be an integer. ');
328 $w->constraint(Range => qw/rating/)->min(1)->max(5)
329 ->message('Must be a number between 1 and 5. ');
330 $w->constraint(Length => qw/title/)->min(5)->max(50)
331 ->message('Must be between 5 and 50 characters. ');
333 # ***NEW: Set filters
334 for my $column (qw/title rating authors/) {
335 $w->filter( HTMLEscape => $column );
336 $w->filter( TrimEdges => $column );
343 The main changes are:
349 The C<Select> element for C<authors> is changed from a single-select
350 drop-down to a multi-select list by adding calls to C<multiple> (set to
351 C<true>) and C<size> (set to the number of rows to display).
355 Four sets of constraints are added to provide validation of the user input.
359 Two filters are run on every field to remove and escape unwanted input.
363 =head2 Rebuild the Form Submission Method to Include Validation
365 Edit C<lib/MyApp/Controller/Books.pm> and change C<hw_create_do> to
366 match the following code (enough of the code is different that you
367 probably want to cut and paste this over code the existing method):
369 sub hw_create_do : Local {
372 # Retrieve the data from the form
373 my $title = $c->request->params->{title};
374 my $rating = $c->request->params->{rating};
375 my $authors = $c->request->params->{authors};
377 # Create the widget and set the action for the form
378 my $w = $self->make_book_widget($c);
379 $w->action($c->uri_for('hw_create_do'));
381 # Validate the form parameters
382 my $result = $w->process($c->req);
384 # Write form (including validation error messages) to
385 # stash variable for use in template
386 $c->stash->{widget_result} = $result;
388 # Were their validation errors?
389 if ($result->has_errors) {
390 # Warn the user at the top of the form that there were errors.
391 # Note that there will also be per-field feedback on
392 # validation errors because of '$w->process($c->req)' above.
393 $c->stash->{error_msg} = 'Validation errors!';
395 # Everything validated OK, so do the create
396 # Call create() on the book model object. Pass the table
397 # columns/field values we want to set as hash values
398 my $book = $c->model('MyAppDB::Book')->create({
403 # Add a record to the join table for this book, mapping to
404 # appropriate author. Note that $authors will be 1 author as
405 # a scalar or ref to list of authors depending on how many the
406 # user selected; the 'ref $authors ?...' handles both cases
407 foreach my $author (ref $authors ? @$authors : $authors) {
408 $book->add_to_book_authors({author_id => $author});
410 # Set a status message for the user
411 $c->stash->{status_msg} = 'Book created';
415 $c->stash->{template} = 'books/hw_form.tt2';
418 The key changes to C<hw_create_do> are:
424 C<hw_create_do> no longer does a C<detach> to C<hw_create> to redisplay
425 the form. Now that C<hw_create_do> has to process the form in order to
426 perform the validation, we go ahead and build a complete set of form
427 presentation logic into C<hw_create_do> (for example, C<hw_create_do>
428 now has a C<$c-E<gt>stash-E<gt>{template}> line). Note that if we
429 process the form in C<hw_create_do> I<and> forward/detach back to
430 <hw_create>, we would end up with C<make_book_widget> being called
431 twice, resulting in a duplicate set of elements being added to the form.
432 (There are other ways to address the "duplicate form rendering" issue --
433 just be aware that it exists.)
437 C<$w-E<gt>process($c-E<gt>req)> is called to run the validation logic.
438 Not only does this set the C<has_errors> flag if validation errors are
439 encountered, it returns a string containing any field-specific warning
444 An C<if> statement checks if any validation errors were encountered. If
445 so, C<$c-E<gt>stash-E<gt>{error_msg}> is set and the input form is
446 redisplayed. If no errors were found, the object is created in a manner
447 similar to the prior version of the C<hw_create_do> method.
451 =head2 Try Out the Form
453 Press C<Ctrl-C> to kill the previous server instance (if it's still running) and restart it:
455 $ script/myapp_server.pl
457 Now try adding a book with various errors: title less than 5 characters,
458 non-numeric rating, a rating of 0 or 6, etc. Also try selecting one,
459 two, and zero authors. When you click Submit, the HTML::Widget
460 C<constraint> items will validate the logic and insert feedback as
463 =head1 Enable C<DBIx::Class::HTMLWidget> Support
465 In this section we will take advantage of some of the "auto-population"
466 features of C<DBIx::Class::HTMLWidget>. Enabling
467 C<DBIx::Class::HTMLWidget> provides two additional methods to your DBIC
476 Takes data from the database and transfers it to your form widget.
480 populate_from_widget()
482 Takes data from a form widget and uses it to update the corresponding
483 records in the database.
487 In other words, the two methods are a mirror image of each other: one
488 reads from the database while the other writes to the database.
490 =head2 Add C<DBIx::Class::HTMLWidget> to DBIC Model
492 In order to use L<DBIx::Class::HTMLWidget|DBIx::Class::HTMLWidget>, we
493 need to add C<HTMLWidget> to the C<load_components> line of DBIC result
494 source files that need to use the C<fill_widget> and
495 C<populate_from_widget> methods. In this case, open
496 C<lib/MyAppDB/Book.pm> and update the C<load_components> line to match:
498 __PACKAGE__->load_components(qw/PK::Auto Core HTMLWidget/);
500 =head2 Use C<populate_from_widget> in C<hw_create_do>
502 Edit C<lib/MyApp/Controller/Books.pm> and update C<hw_create_do> to
503 match the following code:
507 Build an HTML::Widget form for book creation and updates
511 sub hw_create_do : Local {
514 # Create the widget and set the action for the form
515 my $w = $self->make_book_widget($c);
516 $w->action($c->uri_for('hw_create_do'));
518 # Validate the form parameters
519 my $result = $w->process($c->req);
521 # Write form (including validation error messages) to
522 # stash variable for use in template
523 $c->stash->{widget_result} = $result;
525 # Were their validation errors?
526 if ($result->has_errors) {
527 # Warn the user at the top of the form that there were errors.
528 # Note that there will also be per-field feedback on
529 # validation errors because of '$w->process($c->req)' above.
530 $c->stash->{error_msg} = 'Validation errors!';
532 my $book = $c->model('MyAppDB::Book')->new({});
533 $book->populate_from_widget($result);
535 # Add a record to the join table for this book, mapping to
536 # appropriate author. Note that $authors will be 1 author as
537 # a scalar or ref to list of authors depending on how many the
538 # user selected; the 'ref $authors ?...' handles both cases
539 my $authors = $c->request->params->{authors};
540 foreach my $author (ref $authors ? @$authors : $authors) {
541 $book->add_to_book_authors({author_id => $author});
544 # Set a status message for the user
545 $c->stash->{status_msg} = 'Book created';
549 $c->stash->{template} = 'books/hw_form.tt2';
552 In this version of C<hw_create_do> we removed the logic that manually
553 pulled the form variables and used them to call
554 C<$c-E<gt>model('MyAppDB::Book')-E<gt>create> and replaced it with a
555 single call to C<$book-E<gt>populate_from_widget>. Note that we still
556 have to call C<$book-E<gt>add_to_book_authors> once per author because
557 C<populate_from_widget> does not currently handle the relationships
563 Kennedy Clark, C<hkclark@gmail.com>
565 Please report any errors, issues or suggestions to the author. The
566 most recent version of the Catlayst Tutorial can be found at
567 L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Runtime/lib/Catalyst/Manual/Tutorial/>.
569 Copyright 2006, Kennedy Clark, under Creative Commons License
570 (L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).