initial import of new Tutorial stuff from hkclark
[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<Totorial 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::Tutorial03_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 Create, Read, Update, and Delete (CRUD) than we saw in Part 3.  In particular, it looks at a number of techniques that can be useful for the Update portion of CRUD, such as automated form generation, validation of user-entered data, and automated transfer of data between forms and model objects.
58
59 In keeping with the Catalyst (and Perl) spirit of flexibility, there are many different ways approach advanced CRUD operations in a Catalyst environment.  One alternative is to use L<Catalyst::Helper::Controller::Scaffold|Catalyst::Helper::Controller::Scaffold> to instantly construct a set of Controller methods and templates for basic CRUD operations.  Although a popular subject in Quicktime movies that serve as promotional material for various frameworks, more real-world applications require more control.  Other options include L<Data::FormValidator|Data::FormValidator> and L<HTML::FillInForm|HTML::FillInForm>.
60
61 Here, we will make use of the L<HTML::Widget|HTML::Widget> to not only ease form creation, but to also provide validation of the submitted data.  The approached used by the part of the tutorial is to slowly incorporate additional L<HTML::Widget|HTML::Widget> functionality in a step-wise fashion (we start with fairly simple form creation and then move on to more complex and "magical" features such as validation and auto-population/auto-saving).
62
63 B<Note:> Part 8 of the tutorial is optional.  Users who do not which to use L<HTML::Widget|HTML::Widget> may skip this section.
64
65 B<TIP>: Note that all of the code for this part of the tutorial can be pulled from the Catalyst Subversion repository in one step with the following command:
66
67     svn checkout http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial@###
68     IMPORTANT: Does not work yet.  Will be completed for final version.
69
70
71
72 =head1 C<HTML::WIDGET> FORM CREATION
73
74 This section looks at how L<HTML::Widget|HTML::Widget> can be used to add additional functionality to the manually created form from Part 3.
75
76
77 =head2 Add the C<HTML::Widget> Plugin
78
79 Open C<lib/MyApp.pm> in your editor and add the following to the list of plugins (be sure to leave the existing plugins enabled:
80
81     HTML::Widget
82
83
84 =head2 Add a Form Creation Helper Method
85
86 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the following method:
87
88     =head2 make_book_widget
89     
90     Build an HTML::Widget form for book creation and updates
91     
92     =cut
93     
94     sub make_book_widget {
95         my ($self, $c) = @_;
96
97         # Create an HTML::Widget to build the form
98         my $w = $c->widget('book_form')->method('post');
99     
100         # Get authors
101         my @authorObjs = $c->model("MyAppDB::Author")->all();
102         my @authors = map {$_->id => $_->last_name }
103                            sort {$a->last_name cmp $b->last_name} @authorObjs;
104     
105         # Create the form feilds
106         $w->element('Textfield', 'title'  )->label('Title')->size(60);
107         $w->element('Textfield', 'rating' )->label('Rating')->size(1);
108         $w->element('Select',    'authors')->label('Authors')
109             ->options(@authors);
110         $w->element('Submit',    'submit' )->value('submit');
111
112         # Return the widget    
113         return $w;
114     }
115
116 This method provides a central location (so it can be called by multiple actions, such as create and edit) that builds an HTML::Wiget-based form with the appropriate fields.  The "Get Authors" code uses DBIC to retrieve a list of model objects and then uses C<map> to quickly create a hash where the hash keys are the database primary keys from the authors table and the associated values are the last names of the authors.
117
118
119 =head2 Add Actions to Display and Save the Form
120
121 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the following methods:
122
123     =head2 hw_create
124     
125     Build an HTML::Widget form for book creation and updates
126     
127     =cut
128     
129     sub hw_create : Local {
130         my ($self, $c) = @_;
131     
132         # Create the widget and set the action for the form
133         my $w = $self->make_book_widget($c);
134         $w->action($c->uri_for('hw_create_do'));
135     
136         # Write form to stash variable for use in template
137         $c->stash->{widget_result} = $w->result;
138     
139         # Set the template
140         $c->stash->{template} = 'books/hw_form.tt2';
141     }
142     
143     
144     =head2 hw_create_do
145     
146     Build an HTML::Widget form for book creation and updates
147     
148     =cut
149     
150     sub hw_create_do : Local {
151         my ($self, $c) = @_;
152     
153         # Retrieve the data from the form
154         my $title   = $c->request->params->{title};
155         my $rating  = $c->request->params->{rating};
156         my $authors = $c->request->params->{authors};
157     
158         # Call create() on the book model object. Pass the table 
159         # columns/field values we want to set as hash values
160         my $book = $c->model('MyAppDB::Book')->create({
161                 title   => $title,
162                 rating  => $rating
163             });
164         
165         # Add a record to the join table for this book, mapping to 
166         # appropriate author
167         $book->add_to_book_authors({author_id => $authors});
168         
169         # Set a status message for the user
170         $c->stash->{status_msg} = 'Book created';
171     
172         # Use 'hw_create' to redisplay the form
173         $c->detach('hw_create');
174     
175     }
176
177 Note how we use C<make_book_widget> to build the core parts of the form in one location, but we set the action (the URL the form is sent to when the user clicks the 'Submit' button) separately in C<hw_create>.  Doing so allows us to have the same form submit the data to different actions (e.g., C<hw_create_do> for a create operation but C<hw_update_do> to update an existing book object).
178
179
180 =head2 Update the CSS
181
182 Edit C<root/src/ttsite.css> and add the following lines to the bottom of the file:
183
184     label {
185         display: block;
186         width: 10em;
187         position: relative;
188         margin: .5em 0em;
189     }
190     label input {
191         position: absolute;
192         left: 100%;
193     }
194     label select {
195         position: absolute;
196         left: 100%;
197     }
198     .submit {
199         margin-top: 2em;;
200     }
201     .error_messages {
202         color: [% site.col.error %];
203     }
204
205 These changes will display form elements vertically and also show error messages in red.  Note that we are pulling the color scheme settings from the C<root/lib/config/col> file that was created by the TTSite helper.  This allows us to change the color used by various error styles in the CCS from a single location.
206
207
208 =head2 Create a Template Page To Display The Form
209
210 C<root/src/books/hw_form.tt2>
211     [% META title = 'Create/Update Book' %]
212     
213     [% widget_result.as_xml %]
214     
215     <p><a href="[% Catalyst.uri_for('list') %]">Return to book list</a></p>
216
217
218 =head2 Add Links for Create and Update via C<HTML::Widget>
219
220 Open C<root/src/books/list.tt2> in your editor and add the following to the bottom of the existing file:
221
222     <p>
223       HTML::Widget:
224       <a href="[% Catalyst.uri_for('hw_create') %]">Create</a>
225       <a href="[% Catalyst.uri_for('hw_update') %]">Update</a>
226     </p>
227
228
229 =head2 Test The <HTML::Widget> Create Form
230
231 Press C<Ctrl-C> to kill the previous server instance (if it's still running) and restart it:
232
233     $ script/myapp_server.pl
234
235 Login as C<test01>.  Once at the Book List page, click the HTML::Widget "Create" link to display for form produced by C<make_book_widget>.  Fill out the form with the following values: Title = "Internetworking with TCP/IP Vol. II", Rating = "4", and Author = "Comer".  Click Submit, and you will be returned to the Create/Update Book page with a "Book created" status message displayed.  Click "Return to book list" to view the newly created book on the main list.
236
237 Also note that this implementation allows you to can create books with bogus information.  Although we have constrained the authors with the drop-down list, there are no restrictions on items such as the length of the title (for example, you can create a one-letter title) and value for the rating (you can use any number you want, and even non-numeric values with SQLite).  The next section seeks to address this concern.
238
239 B<Note:> Depending on the database you are using and how you established the columns in your tables, the database could obviously provide various levels of "type enforcement" on your data.  The key point being made in the previous paragraph is that the I<web application> itself is not performing any validation.
240
241
242 =head1 C<HTML::WIDGET> VALIDATION AND FILTERING
243
244 Although the use of L<HTML::Widget|HTML::Widget> in the previous section did provide an automated mechanism to build the form, the real power of this module stems from functionality that can automatically validate and filter the user input.  Validation uses constraints to be sure that users input appropriate data (for example, that the email field of a form contains a valid email address).  Filtering can be used to remove extraneous whitespace from fields or to escape meta-characters in user input.
245
246
247 =head2 Add Constraints and Filters to the Widget Creation Method
248
249 Open C<lib/MyApp/Controller/Books.pm> in your editor and update the C<make_book_widget> method to match the following (new sections have been marked with a C<*** NEW:> comment):
250
251     sub make_book_widget {
252         my ($self, $c) = @_;
253
254         # Create an HTML::Widget to build the form
255         my $w = $c->widget('book_form')->method('post');
256             
257         # Get authors
258         my @authorObjs = $c->model("MyAppDB::Author")->all();
259         my @authors = map {$_->id => $_->last_name }
260                            sort {$a->last_name cmp $b->last_name} @authorObjs;
261    
262         # Create the form feilds
263         $w->element('Textfield', 'title'  )->label('Title')->size(60);
264         $w->element('Textfield', 'rating' )->label('Rating')->size(1);
265         # ***NEW: Convert to multi-select list
266         $w->element('Select',    'authors')->label('Authors')
267             ->options(@authors)->multiple(1)->size(3);
268         $w->element('Submit',    'submit' )->value('submit');
269     
270         # ***NEW: Set constraints
271         $w->constraint(All     => qw/title rating authors/)
272             ->message('Required. ');
273         $w->constraint(Integer => qw/rating/)
274             ->message('Must be an integer. ');
275         $w->constraint(Range   => qw/rating/)->min(1)->max(5)
276             ->message('Must be a number between 1 and 5. ');
277         $w->constraint(Length  => qw/title/)->min(5)->max(50)
278             ->message('Must be between 5 and 50 characters. ');
279     
280         # ***NEW: Set filters
281         for my $column (qw/title rating authors/) {
282             $w->filter( HTMLEscape => $column );
283             $w->filter( TrimEdges  => $column );
284         }
285     
286         # Return the widget    
287         return $w;
288     }
289
290 The main changes are:
291
292 =over 4
293
294 =item *
295
296 The C<Select> element for C<authors> is changed from a single-select drop-down to a multi-select list by adding calls to C<multiple> (set to C<true>) and C<size> (set to the number of rows to display).
297
298 =item *
299
300 Four sets of constraints are added to provide validation of the user input.
301
302 =item *
303
304 Two filters are run on every field to remove and escape unwanted input.
305
306 =back
307
308
309 =head2 Rebuild the Form Submission Method to Include Validation
310
311 Edit C<lib/MyApp/Controller/Books.pm> and change C<hw_create_do> to match the following code (enough of the code is different that you probably want to cut and paste this over code the existing method):
312
313     sub hw_create_do : Local {
314         my ($self, $c) = @_;
315     
316         # Retrieve the data from the form
317         my $title   = $c->request->params->{title};
318         my $rating  = $c->request->params->{rating};
319         my $authors = $c->request->params->{authors};
320         
321         # Create the widget and set the action for the form
322         my $w = $self->make_book_widget($c);
323         $w->action($c->uri_for('hw_create_do'));
324     
325         # Validate the form parameters
326         my $result = $w->process($c->req);
327     
328         # Write form (including validation error messages) to
329         # stash variable for use in template
330         $c->stash->{widget_result} = $result;
331     
332         # Were their validation errors?
333         if ($result->has_errors) {
334             # Warn the user at the top of the form that there were errors.
335             # Note that there will also be per-field feedback on
336             # validation errors because of '$w->process($c->req)' above.
337             $c->stash->{error_msg} = 'Validation errors!';
338         } else {
339             # Everything validated OK, so do the create
340             # Call create() on the book model object. Pass the table
341             # columns/field values we want to set as hash values
342             my $book = $c->model('MyAppDB::Book')->create({
343                     title   => $title,
344                     rating  => $rating
345                 });
346     
347             # Add a record to the join table for this book, mapping to
348             # appropriate author.  Note that $authors will be 1 author as
349             # a scalar or ref to list of authors depending on how many the
350             # user selected; the 'ref $authors ?...' handles both cases
351             foreach my $author (ref $authors ? @$authors : $authors) {
352                 $book->add_to_book_authors({author_id => $author});
353             }    
354             # Set a status message for the user
355             $c->stash->{status_msg} = 'Book created';
356         }
357     
358         # Set the template
359         $c->stash->{template} = 'books/hw_form.tt2';
360     }
361
362 The key changes to C<hw_create_do> are:
363
364 =over 4
365
366 =item *
367
368 C<hw_create_do> no longer does a C<detach> to C<hw_create> to redisplay the form.  Now that C<hw_create_do> has to process the form in order to perform the validation, we go ahead and build a complete set of form presentation logic into C<hw_create_do> (for example, C<hw_create_do> now has a C<$c-E<gt>stash-E<gt>{template}> line).  Note that if we process the form in C<hw_create_do> I<and> forward/detach back to <hw_create>, we would end up with C<make_book_widget> being called twice, resulting in a duplicate set of elements being added to the form.
369
370 =item *
371
372 C<$w-E<gt>process($c-E<gt>req)> is called to run the validation logic.  Not only does this set the C<has_errors> flag if validation errors are encountered, it returns a string containing any field-specific warning messages.
373
374 =item *
375
376 An C<if> statement checks if any validation errors were encountered.  If so, C<$c-E<gt>stash-E<gt>{error_msg}> is set and the input form is redisplayed.  If no errors were found, the object is created in a manner similar to the prior version of the C<hw_create_do> method.
377
378 =back
379
380
381 =head2 Try Out the Form
382
383 Press C<Ctrl-C> to kill the previous server instance (if it's still running) and restart it:
384
385     $ script/myapp_server.pl
386
387 Now try adding a book with various errors: title less than 5 characters, non-numeric rating, a rating of 0 or 6, etc.  Also try selecting one, two, and zero authors.  When you click Submit, the HTML::Widget C<constraint> items will validate the logic and insert feedback as appropriate.
388
389
390 =head1 Enable C<DBIx::Class::HTMLWidget> Support
391
392 In this section we will take advantage of some of the "auto-population" features of C<DBIx::Class::HTMLWidget>.  Enabling C<DBIx::Class::HTMLWidget> provides two additional methods to your DBIC model classes: 
393
394 =over 4
395
396 =item *
397
398 fill_wiget()
399
400 Takes data from the database and transfers it to your form widget.
401
402 =item *
403
404 populate_from_widget()
405
406 Takes data from a form widget and uses it to update the corresponding records in the database.
407
408 =back
409
410 In other words, the two methods are a mirror image of each other: one reads from the database while the other writes to the database.
411
412
413 =head2 Add C<DBIx::Class::HTMLWidget> to DBIC Model
414
415 In order to use L<DBIx::Class::HTMLWidget|DBIx::Class::HTMLWidget>, we need to add C<HTMLWidget> to the C<load_components> line of DBIC result source files that need to use the C<fill_widget> and C<populate_from_widget> methods.  In this case, open C<lib/MyAppDB/Book.pm> and update the C<load_components> line to match:
416
417         __PACKAGE__->load_components(qw/PK::Auto Core HTMLWidget/);
418
419
420 =head2 Use C<populate_from_widget> in C<hw_create_do>
421
422 Edit C<lib/MyApp/Controller/Books.pm> and update C<hw_create_do> to match the following code:
423
424     =head2 hw_create_do
425     
426     Build an HTML::Widget form for book creation and updates
427     
428     =cut
429     
430     sub hw_create_do : Local {
431         my ($self, $c) = @_;
432     
433         # Create the widget and set the action for the form
434         my $w = $self->make_book_widget($c);
435         $w->action($c->uri_for('hw_create_do'));
436     
437         # Validate the form parameters
438         my $result = $w->process($c->req);
439     
440         # Write form (including validation error messages) to
441         # stash variable for use in template
442         $c->stash->{widget_result} = $result;
443     
444         # Were their validation errors?
445         if ($result->has_errors) {
446             # Warn the user at the top of the form that there were errors.
447             # Note that there will also be per-field feedback on
448             # validation errors because of '$w->process($c->req)' above.
449             $c->stash->{error_msg} = 'Validation errors!';
450         } else {
451             my $book = $c->model('MyAppDB::Book')->new({});
452             $book->populate_from_widget($result);
453     
454             # Add a record to the join table for this book, mapping to
455             # appropriate author.  Note that $authors will be 1 author as
456             # a scalar or ref to list of authors depending on how many the
457             # user selected; the 'ref $authors ?...' handles both cases
458             my $authors = $c->request->params->{authors};
459             foreach my $author (ref $authors ? @$authors : $authors) {
460                 $book->add_to_book_authors({author_id => $author});
461             }
462     
463             # Set a status message for the user
464             $c->stash->{status_msg} = 'Book created';
465         }
466     
467         # Set the template
468         $c->stash->{template} = 'books/hw_form.tt2';
469     }
470
471 In this version of C<hw_create_do> we removed the logic that manually pulled the form variables and used them to call C<$c-E<gt>model('MyAppDB::Book')-E<gt>create> and replaced it with a single call to C<$book-E<gt>populate_from_widget>.  Note that we still have to call C<$book-E<gt>add_to_book_authors> once per author because C<populate_from_widget> does not currently handle the relationships between tables.
472
473
474
475 =head1 AUTHOR
476
477 Kennedy Clark, C<hkclark@gmail.com>
478
479 Please report any errors, issues or suggestions to the author.
480
481 Copyright 2006, Kennedy Clark. All rights reserved.
482
483 This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
484
485 Version: .94
486