Switch from 'sub base :Path :Args(0)' to 'sub index : Private' for
[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
4d583dd8 5=head1 OVERVIEW
6
7This is B<Part 8 of 9> for the Catalyst tutorial.
8
64ccd8a8 9L<Tutorial Overview|Catalyst::Manual::Tutorial>
4d583dd8 10
11=over 4
12
13=item 1
14
15L<Introduction|Catalyst::Manual::Tutorial::Intro>
16
17=item 2
18
19L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
20
21=item 3
22
64ccd8a8 23L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>
4d583dd8 24
25=item 4
26
27L<Authentication|Catalyst::Manual::Tutorial::Authentication>
28
29=item 5
30
31L<Authorization|Catalyst::Manual::Tutorial::Authorization>
32
33=item 6
34
35L<Debugging|Catalyst::Manual::Tutorial::Debugging>
36
37=item 7
38
39L<Testing|Catalyst::Manual::Tutorial::Testing>
40
41=item 8
42
43B<AdvancedCRUD>
44
45=item 9
46
3c098c71 47L<Appendices|Catalyst::Manual::Tutorial::Appendices>
4d583dd8 48
49=back
50
4d583dd8 51=head1 DESCRIPTION
52
64ccd8a8 53This part of the tutorial explores more advanced functionality for
54Create, Read, Update, and Delete (CRUD) than we saw in Part 3. In
55particular, it looks at a number of techniques that can be useful for
56the Update portion of CRUD, such as automated form generation,
57validation of user-entered data, and automated transfer of data between
58forms and model objects.
59
60In keeping with the Catalyst (and Perl) spirit of flexibility, there are
61many different ways approach advanced CRUD operations in a Catalyst
62environment. One alternative is to use
be16bacd 63L<Catalyst::Helper::Controller::Scaffold|Catalyst::Helper::Controller::Scaffold>
64to instantly construct a set of Controller methods and templates for
65basic CRUD operations. Although a popular subject in Quicktime
66movies that serve as promotional material for various frameworks,
67real-world applications generally require more control. Other
68options include L<Data::FormValidator|Data::FormValidator> and
69L<HTML::FillInForm|HTML::FillInForm>.
70
71Here, we will make use of the L<HTML::Widget|HTML::Widget> to not only
72ease form creation, but to also provide validation of the submitted
73data. The approached used by the part of the tutorial is to slowly
74incorporate additional L<HTML::Widget|HTML::Widget> functionality in a
75step-wise fashion (we start with fairly simple form creation and then
76move on to more complex and "magical" features such as validation and
64ccd8a8 77auto-population/auto-saving).
78
3c098c71 79B<Note:> Part 8 of the tutorial is optional. Users who do not wish to
be16bacd 80use L<HTML::Widget|HTML::Widget> may skip this part.
64ccd8a8 81
82B<TIP>: Note that all of the code for this part of the tutorial can be
83pulled from the Catalyst Subversion repository in one step with the
84following command:
4d583dd8 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
4d583dd8 89=head1 C<HTML::WIDGET> FORM CREATION
90
be16bacd 91This section looks at how L<HTML::Widget|HTML::Widget> can be used to
64ccd8a8 92add additional functionality to the manually created form from Part 3.
4d583dd8 93
4d583dd8 94=head2 Add the C<HTML::Widget> Plugin
95
64ccd8a8 96Open C<lib/MyApp.pm> in your editor and add the following to the list of
3c098c71 97plugins (be sure to leave the existing plugins enabled):
4d583dd8 98
99 HTML::Widget
100
4d583dd8 101=head2 Add a Form Creation Helper Method
102
64ccd8a8 103Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
104following method:
4d583dd8 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) = @_;
be16bacd 114
4d583dd8 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');
be16bacd 129
4d583dd8 130 # Return the widget
131 return $w;
132 }
133
64ccd8a8 134This method provides a central location (so it can be called by multiple
cc548726 135actions, such as C<create> and C<edit>) that builds an HTML::Widget-based
3c098c71 136form with the appropriate fields. The "Get Authors" code uses DBIC to
137retrieve a list of model objects and then uses C<map> to create a hash
138where the hash keys are the database primary keys from the authors table
139and the associated values are the last names of the authors.
4d583dd8 140
141=head2 Add Actions to Display and Save the Form
142
64ccd8a8 143Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
144following methods:
4d583dd8 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
64ccd8a8 200Note how we use C<make_book_widget> to build the core parts of the form
201in one location, but we set the action (the URL the form is sent to when
202the user clicks the 'Submit' button) separately in C<hw_create>. Doing
203so 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
205update an existing book object).
4d583dd8 206
4d583dd8 207=head2 Update the CSS
208
64ccd8a8 209Edit C<root/src/ttsite.css> and add the following lines to the bottom of
210the file:
4d583dd8 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
64ccd8a8 233These changes will display form elements vertically and also show error
234messages in red. Note that we are pulling the color scheme settings
235from the C<root/lib/config/col> file that was created by the TTSite
236helper. This allows us to change the color used by various error styles
3c098c71 237in the CSS from a single location.
4d583dd8 238
239=head2 Create a Template Page To Display The Form
240
be16bacd 241Open C<root/src/books/hw_form.tt2> in your editor and enter the following:
242
4d583dd8 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
4d583dd8 249=head2 Add Links for Create and Update via C<HTML::Widget>
250
64ccd8a8 251Open C<root/src/books/list.tt2> in your editor and add the following to
252the bottom of the existing file:
4d583dd8 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
4d583dd8 260=head2 Test The <HTML::Widget> Create Form
261
64ccd8a8 262Press C<Ctrl-C> to kill the previous server instance (if it's still
263running) and restart it:
4d583dd8 264
265 $ script/myapp_server.pl
266
64ccd8a8 267Login 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
269out the form with the following values: Title = "Internetworking with
270TCP/IP Vol. II", Rating = "4", and Author = "Comer". Click Submit, and
271you will be returned to the Create/Update Book page with a "Book
272created" status message displayed. Click "Return to book list" to view
273the newly created book on the main list.
4d583dd8 274
64ccd8a8 275Also note that this implementation allows you to can create books with
276bogus information. Although we have constrained the authors with the
277drop-down list, there are no restrictions on items such as the length of
278the title (for example, you can create a one-letter title) and value for
279the rating (you can use any number you want, and even non-numeric values
3c098c71 280with SQLite). The next section will address this concern.
4d583dd8 281
64ccd8a8 282B<Note:> Depending on the database you are using and how you established
283the columns in your tables, the database could obviously provide various
284levels of "type enforcement" on your data. The key point being made in
285the previous paragraph is that the I<web application> itself is not
286performing any validation.
4d583dd8 287
4d583dd8 288=head1 C<HTML::WIDGET> VALIDATION AND FILTERING
289
64ccd8a8 290Although the use of L<HTML::Widget|HTML::Widget> in the previous section
291did provide an automated mechanism to build the form, the real power of
292this module stems from functionality that can automatically validate and
293filter the user input. Validation uses constraints to be sure that
294users input appropriate data (for example, that the email field of a
295form contains a valid email address). Filtering can be used to remove
296extraneous whitespace from fields or to escape meta-characters in user
297input.
4d583dd8 298
4d583dd8 299=head2 Add Constraints and Filters to the Widget Creation Method
300
64ccd8a8 301Open C<lib/MyApp/Controller/Books.pm> in your editor and update the
302C<make_book_widget> method to match the following (new sections have
303been marked with a C<*** NEW:> comment):
4d583dd8 304
305 sub make_book_widget {
306 my ($self, $c) = @_;
be16bacd 307
4d583dd8 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;
be16bacd 315
4d583dd8 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
344The main changes are:
345
346=over 4
347
348=item *
349
64ccd8a8 350The C<Select> element for C<authors> is changed from a single-select
351drop-down to a multi-select list by adding calls to C<multiple> (set to
352C<true>) and C<size> (set to the number of rows to display).
4d583dd8 353
354=item *
355
356Four sets of constraints are added to provide validation of the user input.
357
358=item *
359
360Two filters are run on every field to remove and escape unwanted input.
361
362=back
363
4d583dd8 364=head2 Rebuild the Form Submission Method to Include Validation
365
64ccd8a8 366Edit C<lib/MyApp/Controller/Books.pm> and change C<hw_create_do> to
367match the following code (enough of the code is different that you
368probably want to cut and paste this over code the existing method):
4d583dd8 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
419The key changes to C<hw_create_do> are:
420
421=over 4
422
423=item *
424
64ccd8a8 425C<hw_create_do> no longer does a C<detach> to C<hw_create> to redisplay
426the form. Now that C<hw_create_do> has to process the form in order to
427perform the validation, we go ahead and build a complete set of form
428presentation logic into C<hw_create_do> (for example, C<hw_create_do>
429now has a C<$c-E<gt>stash-E<gt>{template}> line). Note that if we
430process 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
432twice, resulting in a duplicate set of elements being added to the form.
4d583dd8 433
434=item *
435
64ccd8a8 436C<$w-E<gt>process($c-E<gt>req)> is called to run the validation logic.
437Not only does this set the C<has_errors> flag if validation errors are
438encountered, it returns a string containing any field-specific warning
439messages.
4d583dd8 440
441=item *
442
64ccd8a8 443An C<if> statement checks if any validation errors were encountered. If
444so, C<$c-E<gt>stash-E<gt>{error_msg}> is set and the input form is
445redisplayed. If no errors were found, the object is created in a manner
446similar to the prior version of the C<hw_create_do> method.
4d583dd8 447
448=back
449
4d583dd8 450=head2 Try Out the Form
451
452Press C<Ctrl-C> to kill the previous server instance (if it's still running) and restart it:
453
454 $ script/myapp_server.pl
455
64ccd8a8 456Now try adding a book with various errors: title less than 5 characters,
457non-numeric rating, a rating of 0 or 6, etc. Also try selecting one,
458two, and zero authors. When you click Submit, the HTML::Widget
459C<constraint> items will validate the logic and insert feedback as
460appropriate.
4d583dd8 461
4d583dd8 462=head1 Enable C<DBIx::Class::HTMLWidget> Support
463
64ccd8a8 464In this section we will take advantage of some of the "auto-population"
465features of C<DBIx::Class::HTMLWidget>. Enabling
466C<DBIx::Class::HTMLWidget> provides two additional methods to your DBIC
467model classes:
4d583dd8 468
469=over 4
470
471=item *
472
cc548726 473fill_widget()
4d583dd8 474
475Takes data from the database and transfers it to your form widget.
476
477=item *
478
479populate_from_widget()
480
64ccd8a8 481Takes data from a form widget and uses it to update the corresponding
482records in the database.
4d583dd8 483
484=back
485
64ccd8a8 486In other words, the two methods are a mirror image of each other: one
487reads from the database while the other writes to the database.
4d583dd8 488
4d583dd8 489=head2 Add C<DBIx::Class::HTMLWidget> to DBIC Model
490
64ccd8a8 491In order to use L<DBIx::Class::HTMLWidget|DBIx::Class::HTMLWidget>, we
492need to add C<HTMLWidget> to the C<load_components> line of DBIC result
493source files that need to use the C<fill_widget> and
494C<populate_from_widget> methods. In this case, open
495C<lib/MyAppDB/Book.pm> and update the C<load_components> line to match:
4d583dd8 496
497 __PACKAGE__->load_components(qw/PK::Auto Core HTMLWidget/);
498
4d583dd8 499=head2 Use C<populate_from_widget> in C<hw_create_do>
500
64ccd8a8 501Edit C<lib/MyApp/Controller/Books.pm> and update C<hw_create_do> to
502match the following code:
4d583dd8 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
64ccd8a8 551In this version of C<hw_create_do> we removed the logic that manually
552pulled the form variables and used them to call
553C<$c-E<gt>model('MyAppDB::Book')-E<gt>create> and replaced it with a
554single call to C<$book-E<gt>populate_from_widget>. Note that we still
555have to call C<$book-E<gt>add_to_book_authors> once per author because
556C<populate_from_widget> does not currently handle the relationships
557between tables.
4d583dd8 558
cc548726 559
4d583dd8 560=head1 AUTHOR
561
562Kennedy Clark, C<hkclark@gmail.com>
563
be16bacd 564Please report any errors, issues or suggestions to the author. The
565most recent version of the Catlayst Tutorial can be found at
566L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Runtime/lib/Catalyst/Manual/Tutorial/>.
4d583dd8 567
cc548726 568Copyright 2006, Kennedy Clark, under Creative Commons License
569(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).