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 approach advanced CRUD operations in a Catalyst
62 environment. One alternative is to use
63 L<Catalyst::Helper::Controller::Scaffold> to instantly construct a set
64 of Controller methods and templates for basic CRUD operations. Although
65 a popular subject in Quicktime movies that serve as promotional material
66 for various frameworks, more real-world applications require more
67 control. Other options include L<Data::FormValidator> and
70 Here, we will make use of the L<HTML::Widget> to not only ease form
71 creation, but to also provide validation of the submitted data. The
72 approached used by the part of the tutorial is to slowly incorporate
73 additional L<HTML::Widget> functionality in a step-wise fashion (we
74 start with fairly simple form creation and then move on to more complex
75 and "magical" features such as validation and
76 auto-population/auto-saving).
78 B<Note:> Part 8 of the tutorial is optional. Users who do not wish to
79 use L<HTML::Widget|HTML::Widget> may skip this section.
81 B<TIP>: Note that all of the code for this part of the tutorial can be
82 pulled from the Catalyst Subversion repository in one step with the
85 svn checkout http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial@###
86 IMPORTANT: Does not work yet. Will be completed for final version.
88 =head1 C<HTML::WIDGET> FORM CREATION
90 This section looks at how L<HTML::Widget> can be used to
91 add additional functionality to the manually created form from Part 3.
93 =head2 Add the C<HTML::Widget> Plugin
95 Open C<lib/MyApp.pm> in your editor and add the following to the list of
96 plugins (be sure to leave the existing plugins enabled):
100 =head2 Add a Form Creation Helper Method
102 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
105 =head2 make_book_widget
107 Build an HTML::Widget form for book creation and updates
111 sub make_book_widget {
114 # Create an HTML::Widget to build the form
115 my $w = $c->widget('book_form')->method('post');
118 my @authorObjs = $c->model("MyAppDB::Author")->all();
119 my @authors = map {$_->id => $_->last_name }
120 sort {$a->last_name cmp $b->last_name} @authorObjs;
122 # Create the form feilds
123 $w->element('Textfield', 'title' )->label('Title')->size(60);
124 $w->element('Textfield', 'rating' )->label('Rating')->size(1);
125 $w->element('Select', 'authors')->label('Authors')
127 $w->element('Submit', 'submit' )->value('submit');
133 This method provides a central location (so it can be called by multiple
134 actions, such as C<create> and C<edit>) that builds an HTML::Wiget-based
135 form with the appropriate fields. The "Get Authors" code uses DBIC to
136 retrieve a list of model objects and then uses C<map> to create a hash
137 where the hash keys are the database primary keys from the authors table
138 and the associated values are the last names of the authors.
140 =head2 Add Actions to Display and Save the Form
142 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
147 Build an HTML::Widget form for book creation and updates
151 sub hw_create : Local {
154 # Create the widget and set the action for the form
155 my $w = $self->make_book_widget($c);
156 $w->action($c->uri_for('hw_create_do'));
158 # Write form to stash variable for use in template
159 $c->stash->{widget_result} = $w->result;
162 $c->stash->{template} = 'books/hw_form.tt2';
168 Build an HTML::Widget form for book creation and updates
172 sub hw_create_do : Local {
175 # Retrieve the data from the form
176 my $title = $c->request->params->{title};
177 my $rating = $c->request->params->{rating};
178 my $authors = $c->request->params->{authors};
180 # Call create() on the book model object. Pass the table
181 # columns/field values we want to set as hash values
182 my $book = $c->model('MyAppDB::Book')->create({
187 # Add a record to the join table for this book, mapping to
189 $book->add_to_book_authors({author_id => $authors});
191 # Set a status message for the user
192 $c->stash->{status_msg} = 'Book created';
194 # Use 'hw_create' to redisplay the form
195 $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 C<root/src/books/hw_form.tt2>
241 [% META title = 'Create/Update Book' %]
243 [% widget_result.as_xml %]
245 <p><a href="[% Catalyst.uri_for('list') %]">Return to book list</a></p>
247 =head2 Add Links for Create and Update via C<HTML::Widget>
249 Open C<root/src/books/list.tt2> in your editor and add the following to
250 the bottom of the existing file:
254 <a href="[% Catalyst.uri_for('hw_create') %]">Create</a>
255 <a href="[% Catalyst.uri_for('hw_update') %]">Update</a>
258 =head2 Test The <HTML::Widget> Create Form
260 Press C<Ctrl-C> to kill the previous server instance (if it's still
261 running) and restart it:
263 $ script/myapp_server.pl
265 Login as C<test01>. Once at the Book List page, click the HTML::Widget
266 "Create" link to display for form produced by C<make_book_widget>. Fill
267 out the form with the following values: Title = "Internetworking with
268 TCP/IP Vol. II", Rating = "4", and Author = "Comer". Click Submit, and
269 you will be returned to the Create/Update Book page with a "Book
270 created" status message displayed. Click "Return to book list" to view
271 the newly created book on the main list.
273 Also note that this implementation allows you to can create books with
274 bogus information. Although we have constrained the authors with the
275 drop-down list, there are no restrictions on items such as the length of
276 the title (for example, you can create a one-letter title) and value for
277 the rating (you can use any number you want, and even non-numeric values
278 with SQLite). The next section will address this concern.
280 B<Note:> Depending on the database you are using and how you established
281 the columns in your tables, the database could obviously provide various
282 levels of "type enforcement" on your data. The key point being made in
283 the previous paragraph is that the I<web application> itself is not
284 performing any validation.
286 =head1 C<HTML::WIDGET> VALIDATION AND FILTERING
288 Although the use of L<HTML::Widget|HTML::Widget> in the previous section
289 did provide an automated mechanism to build the form, the real power of
290 this module stems from functionality that can automatically validate and
291 filter the user input. Validation uses constraints to be sure that
292 users input appropriate data (for example, that the email field of a
293 form contains a valid email address). Filtering can be used to remove
294 extraneous whitespace from fields or to escape meta-characters in user
297 =head2 Add Constraints and Filters to the Widget Creation Method
299 Open C<lib/MyApp/Controller/Books.pm> in your editor and update the
300 C<make_book_widget> method to match the following (new sections have
301 been marked with a C<*** NEW:> comment):
303 sub make_book_widget {
306 # Create an HTML::Widget to build the form
307 my $w = $c->widget('book_form')->method('post');
310 my @authorObjs = $c->model("MyAppDB::Author")->all();
311 my @authors = map {$_->id => $_->last_name }
312 sort {$a->last_name cmp $b->last_name} @authorObjs;
314 # Create the form feilds
315 $w->element('Textfield', 'title' )->label('Title')->size(60);
316 $w->element('Textfield', 'rating' )->label('Rating')->size(1);
317 # ***NEW: Convert to multi-select list
318 $w->element('Select', 'authors')->label('Authors')
319 ->options(@authors)->multiple(1)->size(3);
320 $w->element('Submit', 'submit' )->value('submit');
322 # ***NEW: Set constraints
323 $w->constraint(All => qw/title rating authors/)
324 ->message('Required. ');
325 $w->constraint(Integer => qw/rating/)
326 ->message('Must be an integer. ');
327 $w->constraint(Range => qw/rating/)->min(1)->max(5)
328 ->message('Must be a number between 1 and 5. ');
329 $w->constraint(Length => qw/title/)->min(5)->max(50)
330 ->message('Must be between 5 and 50 characters. ');
332 # ***NEW: Set filters
333 for my $column (qw/title rating authors/) {
334 $w->filter( HTMLEscape => $column );
335 $w->filter( TrimEdges => $column );
342 The main changes are:
348 The C<Select> element for C<authors> is changed from a single-select
349 drop-down to a multi-select list by adding calls to C<multiple> (set to
350 C<true>) and C<size> (set to the number of rows to display).
354 Four sets of constraints are added to provide validation of the user input.
358 Two filters are run on every field to remove and escape unwanted input.
362 =head2 Rebuild the Form Submission Method to Include Validation
364 Edit C<lib/MyApp/Controller/Books.pm> and change C<hw_create_do> to
365 match the following code (enough of the code is different that you
366 probably want to cut and paste this over code the existing method):
368 sub hw_create_do : Local {
371 # Retrieve the data from the form
372 my $title = $c->request->params->{title};
373 my $rating = $c->request->params->{rating};
374 my $authors = $c->request->params->{authors};
376 # Create the widget and set the action for the form
377 my $w = $self->make_book_widget($c);
378 $w->action($c->uri_for('hw_create_do'));
380 # Validate the form parameters
381 my $result = $w->process($c->req);
383 # Write form (including validation error messages) to
384 # stash variable for use in template
385 $c->stash->{widget_result} = $result;
387 # Were their validation errors?
388 if ($result->has_errors) {
389 # Warn the user at the top of the form that there were errors.
390 # Note that there will also be per-field feedback on
391 # validation errors because of '$w->process($c->req)' above.
392 $c->stash->{error_msg} = 'Validation errors!';
394 # Everything validated OK, so do the create
395 # Call create() on the book model object. Pass the table
396 # columns/field values we want to set as hash values
397 my $book = $c->model('MyAppDB::Book')->create({
402 # Add a record to the join table for this book, mapping to
403 # appropriate author. Note that $authors will be 1 author as
404 # a scalar or ref to list of authors depending on how many the
405 # user selected; the 'ref $authors ?...' handles both cases
406 foreach my $author (ref $authors ? @$authors : $authors) {
407 $book->add_to_book_authors({author_id => $author});
409 # Set a status message for the user
410 $c->stash->{status_msg} = 'Book created';
414 $c->stash->{template} = 'books/hw_form.tt2';
417 The key changes to C<hw_create_do> are:
423 C<hw_create_do> no longer does a C<detach> to C<hw_create> to redisplay
424 the form. Now that C<hw_create_do> has to process the form in order to
425 perform the validation, we go ahead and build a complete set of form
426 presentation logic into C<hw_create_do> (for example, C<hw_create_do>
427 now has a C<$c-E<gt>stash-E<gt>{template}> line). Note that if we
428 process the form in C<hw_create_do> I<and> forward/detach back to
429 <hw_create>, we would end up with C<make_book_widget> being called
430 twice, resulting in a duplicate set of elements being added to the form.
434 C<$w-E<gt>process($c-E<gt>req)> is called to run the validation logic.
435 Not only does this set the C<has_errors> flag if validation errors are
436 encountered, it returns a string containing any field-specific warning
441 An C<if> statement checks if any validation errors were encountered. If
442 so, C<$c-E<gt>stash-E<gt>{error_msg}> is set and the input form is
443 redisplayed. If no errors were found, the object is created in a manner
444 similar to the prior version of the C<hw_create_do> method.
448 =head2 Try Out the Form
450 Press C<Ctrl-C> to kill the previous server instance (if it's still running) and restart it:
452 $ script/myapp_server.pl
454 Now try adding a book with various errors: title less than 5 characters,
455 non-numeric rating, a rating of 0 or 6, etc. Also try selecting one,
456 two, and zero authors. When you click Submit, the HTML::Widget
457 C<constraint> items will validate the logic and insert feedback as
460 =head1 Enable C<DBIx::Class::HTMLWidget> Support
462 In this section we will take advantage of some of the "auto-population"
463 features of C<DBIx::Class::HTMLWidget>. Enabling
464 C<DBIx::Class::HTMLWidget> provides two additional methods to your DBIC
473 Takes data from the database and transfers it to your form widget.
477 populate_from_widget()
479 Takes data from a form widget and uses it to update the corresponding
480 records in the database.
484 In other words, the two methods are a mirror image of each other: one
485 reads from the database while the other writes to the database.
487 =head2 Add C<DBIx::Class::HTMLWidget> to DBIC Model
489 In order to use L<DBIx::Class::HTMLWidget|DBIx::Class::HTMLWidget>, we
490 need to add C<HTMLWidget> to the C<load_components> line of DBIC result
491 source files that need to use the C<fill_widget> and
492 C<populate_from_widget> methods. In this case, open
493 C<lib/MyAppDB/Book.pm> and update the C<load_components> line to match:
495 __PACKAGE__->load_components(qw/PK::Auto Core HTMLWidget/);
497 =head2 Use C<populate_from_widget> in C<hw_create_do>
499 Edit C<lib/MyApp/Controller/Books.pm> and update C<hw_create_do> to
500 match the following code:
504 Build an HTML::Widget form for book creation and updates
508 sub hw_create_do : Local {
511 # Create the widget and set the action for the form
512 my $w = $self->make_book_widget($c);
513 $w->action($c->uri_for('hw_create_do'));
515 # Validate the form parameters
516 my $result = $w->process($c->req);
518 # Write form (including validation error messages) to
519 # stash variable for use in template
520 $c->stash->{widget_result} = $result;
522 # Were their validation errors?
523 if ($result->has_errors) {
524 # Warn the user at the top of the form that there were errors.
525 # Note that there will also be per-field feedback on
526 # validation errors because of '$w->process($c->req)' above.
527 $c->stash->{error_msg} = 'Validation errors!';
529 my $book = $c->model('MyAppDB::Book')->new({});
530 $book->populate_from_widget($result);
532 # Add a record to the join table for this book, mapping to
533 # appropriate author. Note that $authors will be 1 author as
534 # a scalar or ref to list of authors depending on how many the
535 # user selected; the 'ref $authors ?...' handles both cases
536 my $authors = $c->request->params->{authors};
537 foreach my $author (ref $authors ? @$authors : $authors) {
538 $book->add_to_book_authors({author_id => $author});
541 # Set a status message for the user
542 $c->stash->{status_msg} = 'Book created';
546 $c->stash->{template} = 'books/hw_form.tt2';
549 In this version of C<hw_create_do> we removed the logic that manually
550 pulled the form variables and used them to call
551 C<$c-E<gt>model('MyAppDB::Book')-E<gt>create> and replaced it with a
552 single call to C<$book-E<gt>populate_from_widget>. Note that we still
553 have to call C<$book-E<gt>add_to_book_authors> once per author because
554 C<populate_from_widget> does not currently handle the relationships
559 Kennedy Clark, C<hkclark@gmail.com>
561 Please report any errors, issues or suggestions to the author.
563 Copyright 2006, Kennedy Clark. All rights reserved.
565 This library is free software; you can redistribute it and/or modify it
566 under the same terms as Perl itself.