Switch from 'sub base :Path :Args(0)' to 'sub index : Private' for
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Manual / Tutorial / AdvancedCRUD.pod
1 =head1 NAME
2
3 Catalyst::Manual::Tutorial::AdvancedCRUD - Catalyst Tutorial - Part 8: Advanced CRUD
4
5 =head1 OVERVIEW
6
7 This is B<Part 8 of 9> for the Catalyst tutorial.
8
9 L<Tutorial Overview|Catalyst::Manual::Tutorial>
10
11 =over 4
12
13 =item 1
14
15 L<Introduction|Catalyst::Manual::Tutorial::Intro>
16
17 =item 2
18
19 L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
20
21 =item 3
22
23 L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>
24
25 =item 4
26
27 L<Authentication|Catalyst::Manual::Tutorial::Authentication>
28
29 =item 5
30
31 L<Authorization|Catalyst::Manual::Tutorial::Authorization>
32
33 =item 6
34
35 L<Debugging|Catalyst::Manual::Tutorial::Debugging>
36
37 =item 7
38
39 L<Testing|Catalyst::Manual::Tutorial::Testing>
40
41 =item 8
42
43 B<AdvancedCRUD>
44
45 =item 9
46
47 L<Appendices|Catalyst::Manual::Tutorial::Appendices>
48
49 =back
50
51 =head1 DESCRIPTION
52
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.
59
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|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>.
70
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).
78
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.
81
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
84 following command:
85
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.
88
89 =head1 C<HTML::WIDGET> FORM CREATION
90
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.
93
94 =head2 Add the C<HTML::Widget> Plugin
95
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):
98
99     HTML::Widget
100
101 =head2 Add a Form Creation Helper Method
102
103 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
104 following method:
105
106     =head2 make_book_widget
107     
108     Build an HTML::Widget form for book creation and updates
109     
110     =cut
111     
112     sub make_book_widget {
113         my ($self, $c) = @_;
114     
115         # Create an HTML::Widget to build the form
116         my $w = $c->widget('book_form')->method('post');
117     
118         # Get authors
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;
122     
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')
127             ->options(@authors);
128         $w->element('Submit',    'submit' )->value('submit');
129     
130         # Return the widget    
131         return $w;
132     }
133
134 This method provides a central location (so it can be called by multiple
135 actions, such as C<create> and C<edit>) that builds an HTML::Widget-based
136 form with the appropriate fields. The "Get Authors" code uses DBIC to
137 retrieve a list of model objects and then uses C<map> to create a hash
138 where the hash keys are the database primary keys from the authors table
139 and the associated values are the last names of the authors.
140
141 =head2 Add Actions to Display and Save the Form
142
143 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
144 following methods:
145
146     =head2 hw_create
147     
148     Build an HTML::Widget form for book creation and updates
149     
150     =cut
151     
152     sub hw_create : Local {
153         my ($self, $c) = @_;
154     
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'));
158     
159         # Write form to stash variable for use in template
160         $c->stash->{widget_result} = $w->result;
161     
162         # Set the template
163         $c->stash->{template} = 'books/hw_form.tt2';
164     }
165     
166     
167     =head2 hw_create_do
168     
169     Build an HTML::Widget form for book creation and updates
170     
171     =cut
172     
173     sub hw_create_do : Local {
174         my ($self, $c) = @_;
175     
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};
180     
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({
184                 title   => $title,
185                 rating  => $rating
186             });
187         
188         # Add a record to the join table for this book, mapping to 
189         # appropriate author
190         $book->add_to_book_authors({author_id => $authors});
191         
192         # Set a status message for the user
193         $c->stash->{status_msg} = 'Book created';
194     
195         # Use 'hw_create' to redisplay the form
196         $c->detach('hw_create');
197     
198     }
199
200 Note how we use C<make_book_widget> to build the core parts of the form
201 in one location, but we set the action (the URL the form is sent to when
202 the user clicks the 'Submit' button) separately in C<hw_create>.  Doing
203 so allows us to have the same form submit the data to different actions
204 (e.g., C<hw_create_do> for a create operation but C<hw_update_do> to
205 update an existing book object).
206
207 =head2 Update the CSS
208
209 Edit C<root/src/ttsite.css> and add the following lines to the bottom of
210 the file:
211
212     label {
213         display: block;
214         width: 10em;
215         position: relative;
216         margin: .5em 0em;
217     }
218     label input {
219         position: absolute;
220         left: 100%;
221     }
222     label select {
223         position: absolute;
224         left: 100%;
225     }
226     .submit {
227         margin-top: 2em;;
228     }
229     .error_messages {
230         color: [% site.col.error %];
231     }
232
233 These changes will display form elements vertically and also show error
234 messages in red.  Note that we are pulling the color scheme settings
235 from the C<root/lib/config/col> file that was created by the TTSite
236 helper.  This allows us to change the color used by various error styles
237 in the CSS from a single location.
238
239 =head2 Create a Template Page To Display The Form
240
241 Open C<root/src/books/hw_form.tt2> in your editor and enter the following:
242
243     [% META title = 'Create/Update Book' %]
244     
245     [% widget_result.as_xml %]
246     
247     <p><a href="[% Catalyst.uri_for('list') %]">Return to book list</a></p>
248
249 =head2 Add Links for Create and Update via C<HTML::Widget>
250
251 Open C<root/src/books/list.tt2> in your editor and add the following to
252 the bottom of the existing file:
253
254     <p>
255       HTML::Widget:
256       <a href="[% Catalyst.uri_for('hw_create') %]">Create</a>
257       <a href="[% Catalyst.uri_for('hw_update') %]">Update</a>
258     </p>
259
260 =head2 Test The <HTML::Widget> Create Form
261
262 Press C<Ctrl-C> to kill the previous server instance (if it's still
263 running) and restart it:
264
265     $ script/myapp_server.pl
266
267 Login as C<test01>.  Once at the Book List page, click the HTML::Widget
268 "Create" link to display for form produced by C<make_book_widget>.  Fill
269 out the form with the following values: Title = "Internetworking with
270 TCP/IP Vol. II", Rating = "4", and Author = "Comer".  Click Submit, and
271 you will be returned to the Create/Update Book page with a "Book
272 created" status message displayed.  Click "Return to book list" to view
273 the newly created book on the main list.
274
275 Also note that this implementation allows you to can create books with
276 bogus information.  Although we have constrained the authors with the
277 drop-down list, there are no restrictions on items such as the length of
278 the title (for example, you can create a one-letter title) and value for
279 the rating (you can use any number you want, and even non-numeric values
280 with SQLite).  The next section will address this concern.
281
282 B<Note:> Depending on the database you are using and how you established
283 the columns in your tables, the database could obviously provide various
284 levels of "type enforcement" on your data.  The key point being made in
285 the previous paragraph is that the I<web application> itself is not
286 performing any validation.
287
288 =head1 C<HTML::WIDGET> VALIDATION AND FILTERING
289
290 Although the use of L<HTML::Widget|HTML::Widget> in the previous section
291 did provide an automated mechanism to build the form, the real power of
292 this module stems from functionality that can automatically validate and
293 filter the user input.  Validation uses constraints to be sure that
294 users input appropriate data (for example, that the email field of a
295 form contains a valid email address).  Filtering can be used to remove
296 extraneous whitespace from fields or to escape meta-characters in user
297 input.
298
299 =head2 Add Constraints and Filters to the Widget Creation Method
300
301 Open C<lib/MyApp/Controller/Books.pm> in your editor and update the
302 C<make_book_widget> method to match the following (new sections have
303 been marked with a C<*** NEW:> comment):
304
305     sub make_book_widget {
306         my ($self, $c) = @_;
307     
308         # Create an HTML::Widget to build the form
309         my $w = $c->widget('book_form')->method('post');
310             
311         # Get authors
312         my @authorObjs = $c->model("MyAppDB::Author")->all();
313         my @authors = map {$_->id => $_->last_name }
314                            sort {$a->last_name cmp $b->last_name} @authorObjs;
315     
316         # Create the form feilds
317         $w->element('Textfield', 'title'  )->label('Title')->size(60);
318         $w->element('Textfield', 'rating' )->label('Rating')->size(1);
319         # ***NEW: Convert to multi-select list
320         $w->element('Select',    'authors')->label('Authors')
321             ->options(@authors)->multiple(1)->size(3);
322         $w->element('Submit',    'submit' )->value('submit');
323     
324         # ***NEW: Set constraints
325         $w->constraint(All     => qw/title rating authors/)
326             ->message('Required. ');
327         $w->constraint(Integer => qw/rating/)
328             ->message('Must be an integer. ');
329         $w->constraint(Range   => qw/rating/)->min(1)->max(5)
330             ->message('Must be a number between 1 and 5. ');
331         $w->constraint(Length  => qw/title/)->min(5)->max(50)
332             ->message('Must be between 5 and 50 characters. ');
333     
334         # ***NEW: Set filters
335         for my $column (qw/title rating authors/) {
336             $w->filter( HTMLEscape => $column );
337             $w->filter( TrimEdges  => $column );
338         }
339     
340         # Return the widget    
341         return $w;
342     }
343
344 The main changes are:
345
346 =over 4
347
348 =item *
349
350 The C<Select> element for C<authors> is changed from a single-select
351 drop-down to a multi-select list by adding calls to C<multiple> (set to
352 C<true>) and C<size> (set to the number of rows to display).
353
354 =item *
355
356 Four sets of constraints are added to provide validation of the user input.
357
358 =item *
359
360 Two filters are run on every field to remove and escape unwanted input.
361
362 =back
363
364 =head2 Rebuild the Form Submission Method to Include Validation
365
366 Edit C<lib/MyApp/Controller/Books.pm> and change C<hw_create_do> to
367 match the following code (enough of the code is different that you
368 probably want to cut and paste this over code the existing method):
369
370     sub hw_create_do : Local {
371         my ($self, $c) = @_;
372     
373         # Retrieve the data from the form
374         my $title   = $c->request->params->{title};
375         my $rating  = $c->request->params->{rating};
376         my $authors = $c->request->params->{authors};
377         
378         # Create the widget and set the action for the form
379         my $w = $self->make_book_widget($c);
380         $w->action($c->uri_for('hw_create_do'));
381     
382         # Validate the form parameters
383         my $result = $w->process($c->req);
384     
385         # Write form (including validation error messages) to
386         # stash variable for use in template
387         $c->stash->{widget_result} = $result;
388     
389         # Were their validation errors?
390         if ($result->has_errors) {
391             # Warn the user at the top of the form that there were errors.
392             # Note that there will also be per-field feedback on
393             # validation errors because of '$w->process($c->req)' above.
394             $c->stash->{error_msg} = 'Validation errors!';
395         } else {
396             # Everything validated OK, so do the create
397             # Call create() on the book model object. Pass the table
398             # columns/field values we want to set as hash values
399             my $book = $c->model('MyAppDB::Book')->create({
400                     title   => $title,
401                     rating  => $rating
402                 });
403     
404             # Add a record to the join table for this book, mapping to
405             # appropriate author.  Note that $authors will be 1 author as
406             # a scalar or ref to list of authors depending on how many the
407             # user selected; the 'ref $authors ?...' handles both cases
408             foreach my $author (ref $authors ? @$authors : $authors) {
409                 $book->add_to_book_authors({author_id => $author});
410             }    
411             # Set a status message for the user
412             $c->stash->{status_msg} = 'Book created';
413         }
414     
415         # Set the template
416         $c->stash->{template} = 'books/hw_form.tt2';
417     }
418
419 The key changes to C<hw_create_do> are:
420
421 =over 4
422
423 =item *
424
425 C<hw_create_do> no longer does a C<detach> to C<hw_create> to redisplay
426 the form.  Now that C<hw_create_do> has to process the form in order to
427 perform the validation, we go ahead and build a complete set of form
428 presentation logic into C<hw_create_do> (for example, C<hw_create_do>
429 now has a C<$c-E<gt>stash-E<gt>{template}> line).  Note that if we
430 process the form in C<hw_create_do> I<and> forward/detach back to
431 <hw_create>, we would end up with C<make_book_widget> being called
432 twice, resulting in a duplicate set of elements being added to the form.
433
434 =item *
435
436 C<$w-E<gt>process($c-E<gt>req)> is called to run the validation logic.
437 Not only does this set the C<has_errors> flag if validation errors are
438 encountered, it returns a string containing any field-specific warning
439 messages.
440
441 =item *
442
443 An C<if> statement checks if any validation errors were encountered.  If
444 so, C<$c-E<gt>stash-E<gt>{error_msg}> is set and the input form is
445 redisplayed.  If no errors were found, the object is created in a manner
446 similar to the prior version of the C<hw_create_do> method.
447
448 =back
449
450 =head2 Try Out the Form
451
452 Press C<Ctrl-C> to kill the previous server instance (if it's still running) and restart it:
453
454     $ script/myapp_server.pl
455
456 Now try adding a book with various errors: title less than 5 characters,
457 non-numeric rating, a rating of 0 or 6, etc.  Also try selecting one,
458 two, and zero authors.  When you click Submit, the HTML::Widget
459 C<constraint> items will validate the logic and insert feedback as
460 appropriate.
461
462 =head1 Enable C<DBIx::Class::HTMLWidget> Support
463
464 In this section we will take advantage of some of the "auto-population"
465 features of C<DBIx::Class::HTMLWidget>.  Enabling
466 C<DBIx::Class::HTMLWidget> provides two additional methods to your DBIC
467 model classes:
468
469 =over 4
470
471 =item *
472
473 fill_widget()
474
475 Takes data from the database and transfers it to your form widget.
476
477 =item *
478
479 populate_from_widget()
480
481 Takes data from a form widget and uses it to update the corresponding
482 records in the database.
483
484 =back
485
486 In other words, the two methods are a mirror image of each other: one
487 reads from the database while the other writes to the database.
488
489 =head2 Add C<DBIx::Class::HTMLWidget> to DBIC Model
490
491 In order to use L<DBIx::Class::HTMLWidget|DBIx::Class::HTMLWidget>, we
492 need to add C<HTMLWidget> to the C<load_components> line of DBIC result
493 source files that need to use the C<fill_widget> and
494 C<populate_from_widget> methods.  In this case, open
495 C<lib/MyAppDB/Book.pm> and update the C<load_components> line to match:
496
497         __PACKAGE__->load_components(qw/PK::Auto Core HTMLWidget/);
498
499 =head2 Use C<populate_from_widget> in C<hw_create_do>
500
501 Edit C<lib/MyApp/Controller/Books.pm> and update C<hw_create_do> to
502 match the following code:
503
504     =head2 hw_create_do
505     
506     Build an HTML::Widget form for book creation and updates
507     
508     =cut
509     
510     sub hw_create_do : Local {
511         my ($self, $c) = @_;
512     
513         # Create the widget and set the action for the form
514         my $w = $self->make_book_widget($c);
515         $w->action($c->uri_for('hw_create_do'));
516     
517         # Validate the form parameters
518         my $result = $w->process($c->req);
519     
520         # Write form (including validation error messages) to
521         # stash variable for use in template
522         $c->stash->{widget_result} = $result;
523     
524         # Were their validation errors?
525         if ($result->has_errors) {
526             # Warn the user at the top of the form that there were errors.
527             # Note that there will also be per-field feedback on
528             # validation errors because of '$w->process($c->req)' above.
529             $c->stash->{error_msg} = 'Validation errors!';
530         } else {
531             my $book = $c->model('MyAppDB::Book')->new({});
532             $book->populate_from_widget($result);
533     
534             # Add a record to the join table for this book, mapping to
535             # appropriate author.  Note that $authors will be 1 author as
536             # a scalar or ref to list of authors depending on how many the
537             # user selected; the 'ref $authors ?...' handles both cases
538             my $authors = $c->request->params->{authors};
539             foreach my $author (ref $authors ? @$authors : $authors) {
540                 $book->add_to_book_authors({author_id => $author});
541             }
542     
543             # Set a status message for the user
544             $c->stash->{status_msg} = 'Book created';
545         }
546     
547         # Set the template
548         $c->stash->{template} = 'books/hw_form.tt2';
549     }
550
551 In this version of C<hw_create_do> we removed the logic that manually
552 pulled the form variables and used them to call
553 C<$c-E<gt>model('MyAppDB::Book')-E<gt>create> and replaced it with a
554 single call to C<$book-E<gt>populate_from_widget>.  Note that we still
555 have to call C<$book-E<gt>add_to_book_authors> once per author because
556 C<populate_from_widget> does not currently handle the relationships
557 between tables.
558
559
560 =head1 AUTHOR
561
562 Kennedy Clark, C<hkclark@gmail.com>
563
564 Please report any errors, issues or suggestions to the author.  The
565 most recent version of the Catlayst Tutorial can be found at
566 L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Runtime/lib/Catalyst/Manual/Tutorial/>.
567
568 Copyright 2006, Kennedy Clark, under Creative Commons License
569 (L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).