Cleaned up Tut files; no substantive changes
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Manual / Tutorial / AdvancedCRUD.pod
CommitLineData
4d583dd8 1=head1 NAME
2
64ccd8a8 3Catalyst::Manual::Tutorial::AdvancedCRUD - Catalyst Tutorial - Part 8: Advanced CRUD
4d583dd8 4
5
6
7=head1 OVERVIEW
8
9This is B<Part 8 of 9> for the Catalyst tutorial.
10
64ccd8a8 11L<Tutorial Overview|Catalyst::Manual::Tutorial>
4d583dd8 12
13=over 4
14
15=item 1
16
17L<Introduction|Catalyst::Manual::Tutorial::Intro>
18
19=item 2
20
21L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
22
23=item 3
24
64ccd8a8 25L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>
4d583dd8 26
27=item 4
28
29L<Authentication|Catalyst::Manual::Tutorial::Authentication>
30
31=item 5
32
33L<Authorization|Catalyst::Manual::Tutorial::Authorization>
34
35=item 6
36
37L<Debugging|Catalyst::Manual::Tutorial::Debugging>
38
39=item 7
40
41L<Testing|Catalyst::Manual::Tutorial::Testing>
42
43=item 8
44
45B<AdvancedCRUD>
46
47=item 9
48
49L<Appendicies|Catalyst::Manual::Tutorial::Appendicies>
50
51=back
52
53
54
55=head1 DESCRIPTION
56
64ccd8a8 57This part of the tutorial explores more advanced functionality for
58Create, Read, Update, and Delete (CRUD) than we saw in Part 3. In
59particular, it looks at a number of techniques that can be useful for
60the Update portion of CRUD, such as automated form generation,
61validation of user-entered data, and automated transfer of data between
62forms and model objects.
63
64In keeping with the Catalyst (and Perl) spirit of flexibility, there are
65many different ways approach advanced CRUD operations in a Catalyst
66environment. One alternative is to use
67L<Catalyst::Helper::Controller::Scaffold|Catalyst::Helper::Controller::Scaffold>
68to instantly construct a set of Controller methods and templates for
69basic CRUD operations. Although a popular subject in Quicktime movies
70that serve as promotional material for various frameworks, more
71real-world applications require more control. Other options include
72L<Data::FormValidator|Data::FormValidator> and
73L<HTML::FillInForm|HTML::FillInForm>.
74
75Here, we will make use of the L<HTML::Widget|HTML::Widget> to not only
76ease form creation, but to also provide validation of the submitted
77data. The approached used by the part of the tutorial is to slowly
78incorporate additional L<HTML::Widget|HTML::Widget> functionality in a
79step-wise fashion (we start with fairly simple form creation and then
80move on to more complex and "magical" features such as validation and
81auto-population/auto-saving).
82
83B<Note:> Part 8 of the tutorial is optional. Users who do not which to
84use L<HTML::Widget|HTML::Widget> may skip this section.
85
86B<TIP>: Note that all of the code for this part of the tutorial can be
87pulled from the Catalyst Subversion repository in one step with the
88following command:
4d583dd8 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
64ccd8a8 97This section looks at how L<HTML::Widget|HTML::Widget> can be used to
98add additional functionality to the manually created form from Part 3.
4d583dd8 99
100
101=head2 Add the C<HTML::Widget> Plugin
102
64ccd8a8 103Open C<lib/MyApp.pm> in your editor and add the following to the list of
104plugins (be sure to leave the existing plugins enabled:
4d583dd8 105
106 HTML::Widget
107
108
109=head2 Add a Form Creation Helper Method
110
64ccd8a8 111Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
112following method:
4d583dd8 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
64ccd8a8 142This method provides a central location (so it can be called by multiple
143actions, such as create and edit) that builds an HTML::Wiget-based form
144with the appropriate fields. The "Get Authors" code uses DBIC to
145retrieve a list of model objects and then uses C<map> to quickly create
146a hash where the hash keys are the database primary keys from the
147authors table and the associated values are the last names of the
148authors.
4d583dd8 149
150
151=head2 Add Actions to Display and Save the Form
152
64ccd8a8 153Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
154following methods:
4d583dd8 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
64ccd8a8 210Note how we use C<make_book_widget> to build the core parts of the form
211in one location, but we set the action (the URL the form is sent to when
212the user clicks the 'Submit' button) separately in C<hw_create>. Doing
213so 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
215update an existing book object).
4d583dd8 216
217
218=head2 Update the CSS
219
64ccd8a8 220Edit C<root/src/ttsite.css> and add the following lines to the bottom of
221the file:
4d583dd8 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
64ccd8a8 244These changes will display form elements vertically and also show error
245messages in red. Note that we are pulling the color scheme settings
246from the C<root/lib/config/col> file that was created by the TTSite
247helper. This allows us to change the color used by various error styles
248in the CCS from a single location.
4d583dd8 249
250
251=head2 Create a Template Page To Display The Form
252
253C<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
64ccd8a8 263Open C<root/src/books/list.tt2> in your editor and add the following to
264the bottom of the existing file:
4d583dd8 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
64ccd8a8 275Press C<Ctrl-C> to kill the previous server instance (if it's still
276running) and restart it:
4d583dd8 277
278 $ script/myapp_server.pl
279
64ccd8a8 280Login 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
282out the form with the following values: Title = "Internetworking with
283TCP/IP Vol. II", Rating = "4", and Author = "Comer". Click Submit, and
284you will be returned to the Create/Update Book page with a "Book
285created" status message displayed. Click "Return to book list" to view
286the newly created book on the main list.
4d583dd8 287
64ccd8a8 288Also note that this implementation allows you to can create books with
289bogus information. Although we have constrained the authors with the
290drop-down list, there are no restrictions on items such as the length of
291the title (for example, you can create a one-letter title) and value for
292the rating (you can use any number you want, and even non-numeric values
293with SQLite). The next section seeks to address this concern.
4d583dd8 294
64ccd8a8 295B<Note:> Depending on the database you are using and how you established
296the columns in your tables, the database could obviously provide various
297levels of "type enforcement" on your data. The key point being made in
298the previous paragraph is that the I<web application> itself is not
299performing any validation.
4d583dd8 300
301
302=head1 C<HTML::WIDGET> VALIDATION AND FILTERING
303
64ccd8a8 304Although the use of L<HTML::Widget|HTML::Widget> in the previous section
305did provide an automated mechanism to build the form, the real power of
306this module stems from functionality that can automatically validate and
307filter the user input. Validation uses constraints to be sure that
308users input appropriate data (for example, that the email field of a
309form contains a valid email address). Filtering can be used to remove
310extraneous whitespace from fields or to escape meta-characters in user
311input.
4d583dd8 312
313
314=head2 Add Constraints and Filters to the Widget Creation Method
315
64ccd8a8 316Open C<lib/MyApp/Controller/Books.pm> in your editor and update the
317C<make_book_widget> method to match the following (new sections have
318been marked with a C<*** NEW:> comment):
4d583dd8 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
359The main changes are:
360
361=over 4
362
363=item *
364
64ccd8a8 365The C<Select> element for C<authors> is changed from a single-select
366drop-down to a multi-select list by adding calls to C<multiple> (set to
367C<true>) and C<size> (set to the number of rows to display).
4d583dd8 368
369=item *
370
371Four sets of constraints are added to provide validation of the user input.
372
373=item *
374
375Two 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
64ccd8a8 382Edit C<lib/MyApp/Controller/Books.pm> and change C<hw_create_do> to
383match the following code (enough of the code is different that you
384probably want to cut and paste this over code the existing method):
4d583dd8 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
435The key changes to C<hw_create_do> are:
436
437=over 4
438
439=item *
440
64ccd8a8 441C<hw_create_do> no longer does a C<detach> to C<hw_create> to redisplay
442the form. Now that C<hw_create_do> has to process the form in order to
443perform the validation, we go ahead and build a complete set of form
444presentation logic into C<hw_create_do> (for example, C<hw_create_do>
445now has a C<$c-E<gt>stash-E<gt>{template}> line). Note that if we
446process 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
448twice, resulting in a duplicate set of elements being added to the form.
4d583dd8 449
450=item *
451
64ccd8a8 452C<$w-E<gt>process($c-E<gt>req)> is called to run the validation logic.
453Not only does this set the C<has_errors> flag if validation errors are
454encountered, it returns a string containing any field-specific warning
455messages.
4d583dd8 456
457=item *
458
64ccd8a8 459An C<if> statement checks if any validation errors were encountered. If
460so, C<$c-E<gt>stash-E<gt>{error_msg}> is set and the input form is
461redisplayed. If no errors were found, the object is created in a manner
462similar to the prior version of the C<hw_create_do> method.
4d583dd8 463
464=back
465
466
467=head2 Try Out the Form
468
469Press C<Ctrl-C> to kill the previous server instance (if it's still running) and restart it:
470
471 $ script/myapp_server.pl
472
64ccd8a8 473Now try adding a book with various errors: title less than 5 characters,
474non-numeric rating, a rating of 0 or 6, etc. Also try selecting one,
475two, and zero authors. When you click Submit, the HTML::Widget
476C<constraint> items will validate the logic and insert feedback as
477appropriate.
4d583dd8 478
479
480=head1 Enable C<DBIx::Class::HTMLWidget> Support
481
64ccd8a8 482In this section we will take advantage of some of the "auto-population"
483features of C<DBIx::Class::HTMLWidget>. Enabling
484C<DBIx::Class::HTMLWidget> provides two additional methods to your DBIC
485model classes:
4d583dd8 486
487=over 4
488
489=item *
490
491fill_wiget()
492
493Takes data from the database and transfers it to your form widget.
494
495=item *
496
497populate_from_widget()
498
64ccd8a8 499Takes data from a form widget and uses it to update the corresponding
500records in the database.
4d583dd8 501
502=back
503
64ccd8a8 504In other words, the two methods are a mirror image of each other: one
505reads from the database while the other writes to the database.
4d583dd8 506
507
508=head2 Add C<DBIx::Class::HTMLWidget> to DBIC Model
509
64ccd8a8 510In order to use L<DBIx::Class::HTMLWidget|DBIx::Class::HTMLWidget>, we
511need to add C<HTMLWidget> to the C<load_components> line of DBIC result
512source files that need to use the C<fill_widget> and
513C<populate_from_widget> methods. In this case, open
514C<lib/MyAppDB/Book.pm> and update the C<load_components> line to match:
4d583dd8 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
64ccd8a8 521Edit C<lib/MyApp/Controller/Books.pm> and update C<hw_create_do> to
522match the following code:
4d583dd8 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
64ccd8a8 571In this version of C<hw_create_do> we removed the logic that manually
572pulled the form variables and used them to call
573C<$c-E<gt>model('MyAppDB::Book')-E<gt>create> and replaced it with a
574single call to C<$book-E<gt>populate_from_widget>. Note that we still
575have to call C<$book-E<gt>add_to_book_authors> once per author because
576C<populate_from_widget> does not currently handle the relationships
577between tables.
4d583dd8 578
579
580
581=head1 AUTHOR
582
583Kennedy Clark, C<hkclark@gmail.com>
584
585Please report any errors, issues or suggestions to the author.
586
587Copyright 2006, Kennedy Clark. All rights reserved.
588
589This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
590
591Version: .94
592