3 Catalyst::Manual::Tutorial::AdvancedCRUD - Catalyst Tutorial - Part 8: Advanced CRUD
9 This is B<Part 8 of 9> for the Catalyst tutorial.
11 L<Tutorial Overview|Catalyst::Manual::Tutorial>
17 L<Introduction|Catalyst::Manual::Tutorial::Intro>
21 L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
25 L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>
29 L<Authentication|Catalyst::Manual::Tutorial::Authentication>
33 L<Authorization|Catalyst::Manual::Tutorial::Authorization>
37 L<Debugging|Catalyst::Manual::Tutorial::Debugging>
41 L<Testing|Catalyst::Manual::Tutorial::Testing>
49 L<Appendicies|Catalyst::Manual::Tutorial::Appendicies>
57 This part of the tutorial explores more advanced functionality for
58 Create, Read, Update, and Delete (CRUD) than we saw in Part 3. In
59 particular, it looks at a number of techniques that can be useful for
60 the Update portion of CRUD, such as automated form generation,
61 validation of user-entered data, and automated transfer of data between
62 forms and model objects.
64 In keeping with the Catalyst (and Perl) spirit of flexibility, there are
65 many different ways approach advanced CRUD operations in a Catalyst
66 environment. One alternative is to use
67 L<Catalyst::Helper::Controller::Scaffold|Catalyst::Helper::Controller::Scaffold>
68 to instantly construct a set of Controller methods and templates for
69 basic CRUD operations. Although a popular subject in Quicktime movies
70 that serve as promotional material for various frameworks, more
71 real-world applications require more control. Other options include
72 L<Data::FormValidator|Data::FormValidator> and
73 L<HTML::FillInForm|HTML::FillInForm>.
75 Here, we will make use of the L<HTML::Widget|HTML::Widget> to not only
76 ease form creation, but to also provide validation of the submitted
77 data. The approached used by the part of the tutorial is to slowly
78 incorporate additional L<HTML::Widget|HTML::Widget> functionality in a
79 step-wise fashion (we start with fairly simple form creation and then
80 move on to more complex and "magical" features such as validation and
81 auto-population/auto-saving).
83 B<Note:> Part 8 of the tutorial is optional. Users who do not which to
84 use L<HTML::Widget|HTML::Widget> may skip this section.
86 B<TIP>: Note that all of the code for this part of the tutorial can be
87 pulled from the Catalyst Subversion repository in one step with the
90 svn checkout http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial@###
91 IMPORTANT: Does not work yet. Will be completed for final version.
95 =head1 C<HTML::WIDGET> FORM CREATION
97 This section looks at how L<HTML::Widget|HTML::Widget> can be used to
98 add additional functionality to the manually created form from Part 3.
101 =head2 Add the C<HTML::Widget> Plugin
103 Open C<lib/MyApp.pm> in your editor and add the following to the list of
104 plugins (be sure to leave the existing plugins enabled:
109 =head2 Add a Form Creation Helper Method
111 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
114 =head2 make_book_widget
116 Build an HTML::Widget form for book creation and updates
120 sub make_book_widget {
123 # Create an HTML::Widget to build the form
124 my $w = $c->widget('book_form')->method('post');
127 my @authorObjs = $c->model("MyAppDB::Author")->all();
128 my @authors = map {$_->id => $_->last_name }
129 sort {$a->last_name cmp $b->last_name} @authorObjs;
131 # Create the form feilds
132 $w->element('Textfield', 'title' )->label('Title')->size(60);
133 $w->element('Textfield', 'rating' )->label('Rating')->size(1);
134 $w->element('Select', 'authors')->label('Authors')
136 $w->element('Submit', 'submit' )->value('submit');
142 This method provides a central location (so it can be called by multiple
143 actions, such as create and edit) that builds an HTML::Wiget-based form
144 with the appropriate fields. The "Get Authors" code uses DBIC to
145 retrieve a list of model objects and then uses C<map> to quickly create
146 a hash where the hash keys are the database primary keys from the
147 authors table and the associated values are the last names of the
151 =head2 Add Actions to Display and Save the Form
153 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
158 Build an HTML::Widget form for book creation and updates
162 sub hw_create : Local {
165 # Create the widget and set the action for the form
166 my $w = $self->make_book_widget($c);
167 $w->action($c->uri_for('hw_create_do'));
169 # Write form to stash variable for use in template
170 $c->stash->{widget_result} = $w->result;
173 $c->stash->{template} = 'books/hw_form.tt2';
179 Build an HTML::Widget form for book creation and updates
183 sub hw_create_do : Local {
186 # Retrieve the data from the form
187 my $title = $c->request->params->{title};
188 my $rating = $c->request->params->{rating};
189 my $authors = $c->request->params->{authors};
191 # Call create() on the book model object. Pass the table
192 # columns/field values we want to set as hash values
193 my $book = $c->model('MyAppDB::Book')->create({
198 # Add a record to the join table for this book, mapping to
200 $book->add_to_book_authors({author_id => $authors});
202 # Set a status message for the user
203 $c->stash->{status_msg} = 'Book created';
205 # Use 'hw_create' to redisplay the form
206 $c->detach('hw_create');
210 Note how we use C<make_book_widget> to build the core parts of the form
211 in one location, but we set the action (the URL the form is sent to when
212 the user clicks the 'Submit' button) separately in C<hw_create>. Doing
213 so allows us to have the same form submit the data to different actions
214 (e.g., C<hw_create_do> for a create operation but C<hw_update_do> to
215 update an existing book object).
218 =head2 Update the CSS
220 Edit C<root/src/ttsite.css> and add the following lines to the bottom of
241 color: [% site.col.error %];
244 These changes will display form elements vertically and also show error
245 messages in red. Note that we are pulling the color scheme settings
246 from the C<root/lib/config/col> file that was created by the TTSite
247 helper. This allows us to change the color used by various error styles
248 in the CCS from a single location.
251 =head2 Create a Template Page To Display The Form
253 C<root/src/books/hw_form.tt2>
254 [% META title = 'Create/Update Book' %]
256 [% widget_result.as_xml %]
258 <p><a href="[% Catalyst.uri_for('list') %]">Return to book list</a></p>
261 =head2 Add Links for Create and Update via C<HTML::Widget>
263 Open C<root/src/books/list.tt2> in your editor and add the following to
264 the bottom of the existing file:
268 <a href="[% Catalyst.uri_for('hw_create') %]">Create</a>
269 <a href="[% Catalyst.uri_for('hw_update') %]">Update</a>
273 =head2 Test The <HTML::Widget> Create Form
275 Press C<Ctrl-C> to kill the previous server instance (if it's still
276 running) and restart it:
278 $ script/myapp_server.pl
280 Login as C<test01>. Once at the Book List page, click the HTML::Widget
281 "Create" link to display for form produced by C<make_book_widget>. Fill
282 out the form with the following values: Title = "Internetworking with
283 TCP/IP Vol. II", Rating = "4", and Author = "Comer". Click Submit, and
284 you will be returned to the Create/Update Book page with a "Book
285 created" status message displayed. Click "Return to book list" to view
286 the newly created book on the main list.
288 Also note that this implementation allows you to can create books with
289 bogus information. Although we have constrained the authors with the
290 drop-down list, there are no restrictions on items such as the length of
291 the title (for example, you can create a one-letter title) and value for
292 the rating (you can use any number you want, and even non-numeric values
293 with SQLite). The next section seeks to address this concern.
295 B<Note:> Depending on the database you are using and how you established
296 the columns in your tables, the database could obviously provide various
297 levels of "type enforcement" on your data. The key point being made in
298 the previous paragraph is that the I<web application> itself is not
299 performing any validation.
302 =head1 C<HTML::WIDGET> VALIDATION AND FILTERING
304 Although the use of L<HTML::Widget|HTML::Widget> in the previous section
305 did provide an automated mechanism to build the form, the real power of
306 this module stems from functionality that can automatically validate and
307 filter the user input. Validation uses constraints to be sure that
308 users input appropriate data (for example, that the email field of a
309 form contains a valid email address). Filtering can be used to remove
310 extraneous whitespace from fields or to escape meta-characters in user
314 =head2 Add Constraints and Filters to the Widget Creation Method
316 Open C<lib/MyApp/Controller/Books.pm> in your editor and update the
317 C<make_book_widget> method to match the following (new sections have
318 been marked with a C<*** NEW:> comment):
320 sub make_book_widget {
323 # Create an HTML::Widget to build the form
324 my $w = $c->widget('book_form')->method('post');
327 my @authorObjs = $c->model("MyAppDB::Author")->all();
328 my @authors = map {$_->id => $_->last_name }
329 sort {$a->last_name cmp $b->last_name} @authorObjs;
331 # Create the form feilds
332 $w->element('Textfield', 'title' )->label('Title')->size(60);
333 $w->element('Textfield', 'rating' )->label('Rating')->size(1);
334 # ***NEW: Convert to multi-select list
335 $w->element('Select', 'authors')->label('Authors')
336 ->options(@authors)->multiple(1)->size(3);
337 $w->element('Submit', 'submit' )->value('submit');
339 # ***NEW: Set constraints
340 $w->constraint(All => qw/title rating authors/)
341 ->message('Required. ');
342 $w->constraint(Integer => qw/rating/)
343 ->message('Must be an integer. ');
344 $w->constraint(Range => qw/rating/)->min(1)->max(5)
345 ->message('Must be a number between 1 and 5. ');
346 $w->constraint(Length => qw/title/)->min(5)->max(50)
347 ->message('Must be between 5 and 50 characters. ');
349 # ***NEW: Set filters
350 for my $column (qw/title rating authors/) {
351 $w->filter( HTMLEscape => $column );
352 $w->filter( TrimEdges => $column );
359 The main changes are:
365 The C<Select> element for C<authors> is changed from a single-select
366 drop-down to a multi-select list by adding calls to C<multiple> (set to
367 C<true>) and C<size> (set to the number of rows to display).
371 Four sets of constraints are added to provide validation of the user input.
375 Two filters are run on every field to remove and escape unwanted input.
380 =head2 Rebuild the Form Submission Method to Include Validation
382 Edit C<lib/MyApp/Controller/Books.pm> and change C<hw_create_do> to
383 match the following code (enough of the code is different that you
384 probably want to cut and paste this over code the existing method):
386 sub hw_create_do : Local {
389 # Retrieve the data from the form
390 my $title = $c->request->params->{title};
391 my $rating = $c->request->params->{rating};
392 my $authors = $c->request->params->{authors};
394 # Create the widget and set the action for the form
395 my $w = $self->make_book_widget($c);
396 $w->action($c->uri_for('hw_create_do'));
398 # Validate the form parameters
399 my $result = $w->process($c->req);
401 # Write form (including validation error messages) to
402 # stash variable for use in template
403 $c->stash->{widget_result} = $result;
405 # Were their validation errors?
406 if ($result->has_errors) {
407 # Warn the user at the top of the form that there were errors.
408 # Note that there will also be per-field feedback on
409 # validation errors because of '$w->process($c->req)' above.
410 $c->stash->{error_msg} = 'Validation errors!';
412 # Everything validated OK, so do the create
413 # Call create() on the book model object. Pass the table
414 # columns/field values we want to set as hash values
415 my $book = $c->model('MyAppDB::Book')->create({
420 # Add a record to the join table for this book, mapping to
421 # appropriate author. Note that $authors will be 1 author as
422 # a scalar or ref to list of authors depending on how many the
423 # user selected; the 'ref $authors ?...' handles both cases
424 foreach my $author (ref $authors ? @$authors : $authors) {
425 $book->add_to_book_authors({author_id => $author});
427 # Set a status message for the user
428 $c->stash->{status_msg} = 'Book created';
432 $c->stash->{template} = 'books/hw_form.tt2';
435 The key changes to C<hw_create_do> are:
441 C<hw_create_do> no longer does a C<detach> to C<hw_create> to redisplay
442 the form. Now that C<hw_create_do> has to process the form in order to
443 perform the validation, we go ahead and build a complete set of form
444 presentation logic into C<hw_create_do> (for example, C<hw_create_do>
445 now has a C<$c-E<gt>stash-E<gt>{template}> line). Note that if we
446 process the form in C<hw_create_do> I<and> forward/detach back to
447 <hw_create>, we would end up with C<make_book_widget> being called
448 twice, resulting in a duplicate set of elements being added to the form.
452 C<$w-E<gt>process($c-E<gt>req)> is called to run the validation logic.
453 Not only does this set the C<has_errors> flag if validation errors are
454 encountered, it returns a string containing any field-specific warning
459 An C<if> statement checks if any validation errors were encountered. If
460 so, C<$c-E<gt>stash-E<gt>{error_msg}> is set and the input form is
461 redisplayed. If no errors were found, the object is created in a manner
462 similar to the prior version of the C<hw_create_do> method.
467 =head2 Try Out the Form
469 Press C<Ctrl-C> to kill the previous server instance (if it's still running) and restart it:
471 $ script/myapp_server.pl
473 Now try adding a book with various errors: title less than 5 characters,
474 non-numeric rating, a rating of 0 or 6, etc. Also try selecting one,
475 two, and zero authors. When you click Submit, the HTML::Widget
476 C<constraint> items will validate the logic and insert feedback as
480 =head1 Enable C<DBIx::Class::HTMLWidget> Support
482 In this section we will take advantage of some of the "auto-population"
483 features of C<DBIx::Class::HTMLWidget>. Enabling
484 C<DBIx::Class::HTMLWidget> provides two additional methods to your DBIC
493 Takes data from the database and transfers it to your form widget.
497 populate_from_widget()
499 Takes data from a form widget and uses it to update the corresponding
500 records in the database.
504 In other words, the two methods are a mirror image of each other: one
505 reads from the database while the other writes to the database.
508 =head2 Add C<DBIx::Class::HTMLWidget> to DBIC Model
510 In order to use L<DBIx::Class::HTMLWidget|DBIx::Class::HTMLWidget>, we
511 need to add C<HTMLWidget> to the C<load_components> line of DBIC result
512 source files that need to use the C<fill_widget> and
513 C<populate_from_widget> methods. In this case, open
514 C<lib/MyAppDB/Book.pm> and update the C<load_components> line to match:
516 __PACKAGE__->load_components(qw/PK::Auto Core HTMLWidget/);
519 =head2 Use C<populate_from_widget> in C<hw_create_do>
521 Edit C<lib/MyApp/Controller/Books.pm> and update C<hw_create_do> to
522 match the following code:
526 Build an HTML::Widget form for book creation and updates
530 sub hw_create_do : Local {
533 # Create the widget and set the action for the form
534 my $w = $self->make_book_widget($c);
535 $w->action($c->uri_for('hw_create_do'));
537 # Validate the form parameters
538 my $result = $w->process($c->req);
540 # Write form (including validation error messages) to
541 # stash variable for use in template
542 $c->stash->{widget_result} = $result;
544 # Were their validation errors?
545 if ($result->has_errors) {
546 # Warn the user at the top of the form that there were errors.
547 # Note that there will also be per-field feedback on
548 # validation errors because of '$w->process($c->req)' above.
549 $c->stash->{error_msg} = 'Validation errors!';
551 my $book = $c->model('MyAppDB::Book')->new({});
552 $book->populate_from_widget($result);
554 # Add a record to the join table for this book, mapping to
555 # appropriate author. Note that $authors will be 1 author as
556 # a scalar or ref to list of authors depending on how many the
557 # user selected; the 'ref $authors ?...' handles both cases
558 my $authors = $c->request->params->{authors};
559 foreach my $author (ref $authors ? @$authors : $authors) {
560 $book->add_to_book_authors({author_id => $author});
563 # Set a status message for the user
564 $c->stash->{status_msg} = 'Book created';
568 $c->stash->{template} = 'books/hw_form.tt2';
571 In this version of C<hw_create_do> we removed the logic that manually
572 pulled the form variables and used them to call
573 C<$c-E<gt>model('MyAppDB::Book')-E<gt>create> and replaced it with a
574 single call to C<$book-E<gt>populate_from_widget>. Note that we still
575 have to call C<$book-E<gt>add_to_book_authors> once per author because
576 C<populate_from_widget> does not currently handle the relationships
583 Kennedy Clark, C<hkclark@gmail.com>
585 Please report any errors, issues or suggestions to the author.
587 Copyright 2006, Kennedy Clark. All rights reserved.
589 This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.