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