Cleaned up Tut files; no substantive changes
[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
6
7 =head1 OVERVIEW
8
9 This is B<Part 8 of 9> for the Catalyst tutorial.
10
11 L<Tutorial Overview|Catalyst::Manual::Tutorial>
12
13 =over 4
14
15 =item 1
16
17 L<Introduction|Catalyst::Manual::Tutorial::Intro>
18
19 =item 2
20
21 L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
22
23 =item 3
24
25 L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>
26
27 =item 4
28
29 L<Authentication|Catalyst::Manual::Tutorial::Authentication>
30
31 =item 5
32
33 L<Authorization|Catalyst::Manual::Tutorial::Authorization>
34
35 =item 6
36
37 L<Debugging|Catalyst::Manual::Tutorial::Debugging>
38
39 =item 7
40
41 L<Testing|Catalyst::Manual::Tutorial::Testing>
42
43 =item 8
44
45 B<AdvancedCRUD>
46
47 =item 9
48
49 L<Appendicies|Catalyst::Manual::Tutorial::Appendicies>
50
51 =back
52
53
54
55 =head1 DESCRIPTION
56
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.
63
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>.
74
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).
82
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.
85
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
88 following command:
89
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.
92
93
94
95 =head1 C<HTML::WIDGET> FORM CREATION
96
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.
99
100
101 =head2 Add the C<HTML::Widget> Plugin
102
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:
105
106     HTML::Widget
107
108
109 =head2 Add a Form Creation Helper Method
110
111 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
112 following method:
113
114     =head2 make_book_widget
115     
116     Build an HTML::Widget form for book creation and updates
117     
118     =cut
119     
120     sub make_book_widget {
121         my ($self, $c) = @_;
122
123         # Create an HTML::Widget to build the form
124         my $w = $c->widget('book_form')->method('post');
125     
126         # Get authors
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;
130     
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')
135             ->options(@authors);
136         $w->element('Submit',    'submit' )->value('submit');
137
138         # Return the widget    
139         return $w;
140     }
141
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
148 authors.
149
150
151 =head2 Add Actions to Display and Save the Form
152
153 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
154 following methods:
155
156     =head2 hw_create
157     
158     Build an HTML::Widget form for book creation and updates
159     
160     =cut
161     
162     sub hw_create : Local {
163         my ($self, $c) = @_;
164     
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'));
168     
169         # Write form to stash variable for use in template
170         $c->stash->{widget_result} = $w->result;
171     
172         # Set the template
173         $c->stash->{template} = 'books/hw_form.tt2';
174     }
175     
176     
177     =head2 hw_create_do
178     
179     Build an HTML::Widget form for book creation and updates
180     
181     =cut
182     
183     sub hw_create_do : Local {
184         my ($self, $c) = @_;
185     
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};
190     
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({
194                 title   => $title,
195                 rating  => $rating
196             });
197         
198         # Add a record to the join table for this book, mapping to 
199         # appropriate author
200         $book->add_to_book_authors({author_id => $authors});
201         
202         # Set a status message for the user
203         $c->stash->{status_msg} = 'Book created';
204     
205         # Use 'hw_create' to redisplay the form
206         $c->detach('hw_create');
207     
208     }
209
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).
216
217
218 =head2 Update the CSS
219
220 Edit C<root/src/ttsite.css> and add the following lines to the bottom of
221 the file:
222
223     label {
224         display: block;
225         width: 10em;
226         position: relative;
227         margin: .5em 0em;
228     }
229     label input {
230         position: absolute;
231         left: 100%;
232     }
233     label select {
234         position: absolute;
235         left: 100%;
236     }
237     .submit {
238         margin-top: 2em;;
239     }
240     .error_messages {
241         color: [% site.col.error %];
242     }
243
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.
249
250
251 =head2 Create a Template Page To Display The Form
252
253 C<root/src/books/hw_form.tt2>
254     [% META title = 'Create/Update Book' %]
255     
256     [% widget_result.as_xml %]
257     
258     <p><a href="[% Catalyst.uri_for('list') %]">Return to book list</a></p>
259
260
261 =head2 Add Links for Create and Update via C<HTML::Widget>
262
263 Open C<root/src/books/list.tt2> in your editor and add the following to
264 the bottom of the existing file:
265
266     <p>
267       HTML::Widget:
268       <a href="[% Catalyst.uri_for('hw_create') %]">Create</a>
269       <a href="[% Catalyst.uri_for('hw_update') %]">Update</a>
270     </p>
271
272
273 =head2 Test The <HTML::Widget> Create Form
274
275 Press C<Ctrl-C> to kill the previous server instance (if it's still
276 running) and restart it:
277
278     $ script/myapp_server.pl
279
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.
287
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.
294
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.
300
301
302 =head1 C<HTML::WIDGET> VALIDATION AND FILTERING
303
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
311 input.
312
313
314 =head2 Add Constraints and Filters to the Widget Creation Method
315
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):
319
320     sub make_book_widget {
321         my ($self, $c) = @_;
322
323         # Create an HTML::Widget to build the form
324         my $w = $c->widget('book_form')->method('post');
325             
326         # Get authors
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;
330    
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');
338     
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. ');
348     
349         # ***NEW: Set filters
350         for my $column (qw/title rating authors/) {
351             $w->filter( HTMLEscape => $column );
352             $w->filter( TrimEdges  => $column );
353         }
354     
355         # Return the widget    
356         return $w;
357     }
358
359 The main changes are:
360
361 =over 4
362
363 =item *
364
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).
368
369 =item *
370
371 Four sets of constraints are added to provide validation of the user input.
372
373 =item *
374
375 Two filters are run on every field to remove and escape unwanted input.
376
377 =back
378
379
380 =head2 Rebuild the Form Submission Method to Include Validation
381
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):
385
386     sub hw_create_do : Local {
387         my ($self, $c) = @_;
388     
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};
393         
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'));
397     
398         # Validate the form parameters
399         my $result = $w->process($c->req);
400     
401         # Write form (including validation error messages) to
402         # stash variable for use in template
403         $c->stash->{widget_result} = $result;
404     
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!';
411         } else {
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({
416                     title   => $title,
417                     rating  => $rating
418                 });
419     
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});
426             }    
427             # Set a status message for the user
428             $c->stash->{status_msg} = 'Book created';
429         }
430     
431         # Set the template
432         $c->stash->{template} = 'books/hw_form.tt2';
433     }
434
435 The key changes to C<hw_create_do> are:
436
437 =over 4
438
439 =item *
440
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.
449
450 =item *
451
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
455 messages.
456
457 =item *
458
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.
463
464 =back
465
466
467 =head2 Try Out the Form
468
469 Press C<Ctrl-C> to kill the previous server instance (if it's still running) and restart it:
470
471     $ script/myapp_server.pl
472
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
477 appropriate.
478
479
480 =head1 Enable C<DBIx::Class::HTMLWidget> Support
481
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
485 model classes:
486
487 =over 4
488
489 =item *
490
491 fill_wiget()
492
493 Takes data from the database and transfers it to your form widget.
494
495 =item *
496
497 populate_from_widget()
498
499 Takes data from a form widget and uses it to update the corresponding
500 records in the database.
501
502 =back
503
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.
506
507
508 =head2 Add C<DBIx::Class::HTMLWidget> to DBIC Model
509
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:
515
516         __PACKAGE__->load_components(qw/PK::Auto Core HTMLWidget/);
517
518
519 =head2 Use C<populate_from_widget> in C<hw_create_do>
520
521 Edit C<lib/MyApp/Controller/Books.pm> and update C<hw_create_do> to
522 match the following code:
523
524     =head2 hw_create_do
525     
526     Build an HTML::Widget form for book creation and updates
527     
528     =cut
529     
530     sub hw_create_do : Local {
531         my ($self, $c) = @_;
532     
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'));
536     
537         # Validate the form parameters
538         my $result = $w->process($c->req);
539     
540         # Write form (including validation error messages) to
541         # stash variable for use in template
542         $c->stash->{widget_result} = $result;
543     
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!';
550         } else {
551             my $book = $c->model('MyAppDB::Book')->new({});
552             $book->populate_from_widget($result);
553     
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});
561             }
562     
563             # Set a status message for the user
564             $c->stash->{status_msg} = 'Book created';
565         }
566     
567         # Set the template
568         $c->stash->{template} = 'books/hw_form.tt2';
569     }
570
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
577 between tables.
578
579
580
581 =head1 AUTHOR
582
583 Kennedy Clark, C<hkclark@gmail.com>
584
585 Please report any errors, issues or suggestions to the author.
586
587 Copyright 2006, Kennedy Clark. All rights reserved.
588
589 This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
590
591 Version: .94
592