Add select to form and comments to config file
[catagits/Catalyst-Manual.git] / lib / Catalyst / Manual / Tutorial / BasicCRUD.pod
CommitLineData
d442cc9f 1=head1 NAME
2
3533daff 3Catalyst::Manual::Tutorial::BasicCRUD - Catalyst Tutorial - Part 4: Basic CRUD
d442cc9f 4
5
6=head1 OVERVIEW
7
3533daff 8This is B<Part 4 of 10> for the Catalyst tutorial.
d442cc9f 9
10L<Tutorial Overview|Catalyst::Manual::Tutorial>
11
12=over 4
13
14=item 1
15
16L<Introduction|Catalyst::Manual::Tutorial::Intro>
17
18=item 2
19
20L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
21
22=item 3
23
3533daff 24L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
d442cc9f 25
26=item 4
27
3533daff 28B<Basic CRUD>
d442cc9f 29
30=item 5
31
3533daff 32L<Authentication|Catalyst::Manual::Tutorial::Authentication>
d442cc9f 33
34=item 6
35
3533daff 36L<Authorization|Catalyst::Manual::Tutorial::Authorization>
d442cc9f 37
38=item 7
39
3533daff 40L<Debugging|Catalyst::Manual::Tutorial::Debugging>
d442cc9f 41
42=item 8
43
3533daff 44L<Testing|Catalyst::Manual::Tutorial::Testing>
d442cc9f 45
46=item 9
47
3533daff 48L<Advanced CRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
49
50=item 10
51
d442cc9f 52L<Appendices|Catalyst::Manual::Tutorial::Appendices>
53
54=back
55
56
d442cc9f 57=head1 DESCRIPTION
58
59This part of the tutorial builds on the fairly primitive application
3533daff 60created in Part 3 to add basic support for Create, Read, Update, and
d442cc9f 61Delete (CRUD) of C<Book> objects. Note that the 'list' function in Part
622 already implements the Read portion of CRUD (although Read normally
63refers to reading a single object; you could implement full read
64functionality using the techniques introduced below). This section will
65focus on the Create and Delete aspects of CRUD. More advanced
66capabilities, including full Update functionality, will be addressed in
3533daff 67Part 9.
d442cc9f 68
69You can checkout the source code for this example from the catalyst
70subversion repository as per the instructions in
71L<Catalyst::Manual::Tutorial::Intro>
72
3533daff 73
d442cc9f 74=head1 FORMLESS SUBMISSION
75
3533daff 76Our initial attempt at object creation will utilize the "URL
77arguments" feature of Catalyst (we will employ the more common form-
78based submission in the sections that follow).
d442cc9f 79
80
81=head2 Include a Create Action in the Books Controller
82
83Edit C<lib/MyApp/Controller/Books.pm> and enter the following method:
84
85 =head2 url_create
86
87 Create a book with the supplied title, rating, and author
88
89 =cut
90
91 sub url_create : Local {
92 # In addition to self & context, get the title, rating, &
93 # author_id args from the URL. Note that Catalyst automatically
94 # puts extra information after the "/<controller_name>/<action_name/"
95 # into @_
96 my ($self, $c, $title, $rating, $author_id) = @_;
97
98 # Call create() on the book model object. Pass the table
99 # columns/field values we want to set as hash values
d0496197 100 my $book = $c->model('DB::Books')->create({
d442cc9f 101 title => $title,
102 rating => $rating
103 });
104
105 # Add a record to the join table for this book, mapping to
106 # appropriate author
107 $book->add_to_book_authors({author_id => $author_id});
108 # Note: Above is a shortcut for this:
109 # $book->create_related('book_authors', {author_id => $author_id});
110
111 # Assign the Book object to the stash for display in the view
112 $c->stash->{book} = $book;
113
114 # This is a hack to disable XSUB processing in Data::Dumper
115 # (it's used in the view). This is a work-around for a bug in
116 # the interaction of some versions or Perl, Data::Dumper & DBIC.
117 # You won't need this if you aren't using Data::Dumper (or if
118 # you are running DBIC 0.06001 or greater), but adding it doesn't
119 # hurt anything either.
120 $Data::Dumper::Useperl = 1;
121
122 # Set the TT template to use
123 $c->stash->{template} = 'books/create_done.tt2';
124 }
125
126Notice that Catalyst takes "extra slash-separated information" from the
127URL and passes it as arguments in C<@_>. The C<url_create> action then
128uses a simple call to the DBIC C<create> method to add the requested
129information to the database (with a separate call to
130C<add_to_book_authors> to update the join table). As do virtually all
131controller methods (at least the ones that directly handle user input),
132it then sets the template that should handle this request.
133
134
135=head2 Include a Template for the C<url_create> Action:
136
137Edit C<root/src/books/create_done.tt2> and then enter:
138
139 [% # Use the TT Dumper plugin to Data::Dumper variables to the browser -%]
140 [% # Not a good idea for production use, though. :-) 'Indent=1' is -%]
141 [% # optional, but prevents "massive indenting" of deeply nested objects -%]
142 [% USE Dumper(Indent=1) -%]
143
144 [% # Set the page title. META can 'go back' and set values in templates -%]
145 [% # that have been processed 'before' this template (here it's for -%]
146 [% # root/lib/site/html and root/lib/site/header). Note that META on -%]
147 [% # simple strings (e.g., no variable interpolation). -%]
148 [% META title = 'Book Created' %]
149
150 [% # Output information about the record that was added. First title. -%]
151 <p>Added book '[% book.title %]'
152
153 [% # Output the last name of the first author. This is complicated by an -%]
154 [% # issue in TT 2.15 where blessed hash objects are not handled right. -%]
155 [% # First, fetch 'book.authors' from the DB once. -%]
156 [% authors = book.authors %]
157 [% # Now use IF statements to test if 'authors.first' is "working". If so, -%]
158 [% # we use it. Otherwise we use a hack that seems to keep TT 2.15 happy. -%]
159 by '[% authors.first.last_name IF authors.first;
160 authors.list.first.value.last_name IF ! authors.first %]'
161
162 [% # Output the rating for the book that was added -%]
163 with a rating of [% book.rating %].</p>
164
165 [% # Provide a link back to the list page -%]
166 [% # 'uri_for()' builds a full URI; e.g., 'http://localhost:3000/books/list' -%]
167 <p><a href="[% Catalyst.uri_for('/books/list') %]">Return to list</a></p>
168
169 [% # Try out the TT Dumper (for development only!) -%]
170 <pre>
171 Dump of the 'book' variable:
172 [% Dumper.dump(book) %]
173 </pre>
174
175The TT C<USE> directive allows access to a variety of plugin modules (TT
176plugins, that is, not Catalyst plugins) to add extra functionality to
177the base TT capabilities. Here, the plugin allows L<Data::Dumper>
178"pretty printing" of objects and variables. Other than that, the rest
3533daff 179of the code should be familiar from the examples in Part 3.
d442cc9f 180
181B<IMPORTANT NOTE> As mentioned earlier, the C<MyApp::View::TT.pm> view
182class created by TTSite redefines the name used to access the Catalyst
183context object in TT templates from the usual C<c> to C<Catalyst>.
184
185=head2 Try the C<url_create> Feature
186
187If the application is still running from before, use C<Ctrl-C> to kill
188it. Then restart the server:
189
190 $ script/myapp_server.pl
191
192Note that new path for C</books/url_create> appears in the startup debug
193output.
194
195B<TIP>: You can use C<script/myapp_server.pl -r> to have the development
196server auto-detect changed files and reload itself (if your browser acts
197odd, you should also try throwing in a C<-k>). If you make changes to
198the TT templates only, you do not need to reload the development server
199(only changes to "compiled code" such as Controller and Model C<.pm>
200files require a reload).
201
202Next, use your browser to enter the following URL:
203
204 http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4
205
206Your browser should display " Added book 'TCPIP_Illustrated_Vol-2' by
207'Stevens' with a rating of 5." along with a dump of the new book model
208object. You should also see the following DBIC debug messages displayed
3533daff 209in the development server log messages if you have DBIC_TRACE set:
d442cc9f 210
211 INSERT INTO books (rating, title) VALUES (?, ?): `5', `TCPIP_Illustrated_Vol-2'
212 INSERT INTO book_authors (author_id, book_id) VALUES (?, ?): `4', `6'
213 SELECT author.id, author.first_name, author.last_name
214 FROM book_authors me JOIN authors author
215 ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '6'
216
217The C<INSERT> statements are obviously adding the book and linking it to
218the existing record for Richard Stevens. The C<SELECT> statement results
219from DBIC automatically fetching the book for the C<Dumper.dump(book)>.
220
221If you then click the "Return to list" link, you should find that there
222are now six books shown (if necessary, Shift-Reload your browser at the
223C</books/list> page).
224
225Then I<add 2 more copies of the same book> so that we have some extras for
226our delete logic that will be coming up soon. Enter the same URL above
227two more times (or refresh your browser twice if it still contains this
228URL):
229
230 http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4
231
232You should be able to click "Return to list" and now see 3 copies of
233"TCP_Illustrated_Vol-2".
234
235
236=head1 MANUALLY BUILDING A CREATE FORM
237
238Although the C<url_create> action in the previous step does begin to
239reveal the power and flexibility of both Catalyst and DBIC, it's
240obviously not a very realistic example of how users should be expected
241to enter data. This section begins to address that concern.
242
243
244=head2 Add Method to Display The Form
245
246Edit C<lib/MyApp/Controller/Books.pm> and add the following method:
247
248 =head2 form_create
249
250 Display form to collect information for book to create
251
252 =cut
253
254 sub form_create : Local {
255 my ($self, $c) = @_;
256
257 # Set the TT template to use
258 $c->stash->{template} = 'books/form_create.tt2';
259 }
260
261This action simply invokes a view containing a book creation form.
262
263=head2 Add a Template for the Form
264
265Open C<root/src/books/form_create.tt2> in your editor and enter:
266
267 [% META title = 'Manual Form Book Create' -%]
268
269 <form method="post" action="[% Catalyst.uri_for('form_create_do') %]">
270 <table>
271 <tr><td>Title:</td><td><input type="text" name="title"></td></tr>
272 <tr><td>Rating:</td><td><input type="text" name="rating"></td></tr>
273 <tr><td>Author ID:</td><td><input type="text" name="author_id"></td></tr>
274 </table>
275 <input type="submit" name="Submit" value="Submit">
276 </form>
277
278Note that we have specified the target of the form data as
279C<form_create_do>, the method created in the section that follows.
280
281=head2 Add a Method to Process Form Values and Update Database
282
283Edit C<lib/MyApp/Controller/Books.pm> and add the following method to
284save the form information to the database:
285
286 =head2 form_create_do
287
288 Take information from form and add to database
289
290 =cut
291
292 sub form_create_do : Local {
293 my ($self, $c) = @_;
294
295 # Retrieve the values from the form
296 my $title = $c->request->params->{title} || 'N/A';
297 my $rating = $c->request->params->{rating} || 'N/A';
298 my $author_id = $c->request->params->{author_id} || '1';
299
300 # Create the book
d0496197 301 my $book = $c->model('DB::Books')->create({
d442cc9f 302 title => $title,
303 rating => $rating,
304 });
305 # Handle relationship with author
306 $book->add_to_book_authors({author_id => $author_id});
307
308 # Store new model object in stash
309 $c->stash->{book} = $book;
310
311 # Avoid Data::Dumper issue mentioned earlier
312 # You can probably omit this
313 $Data::Dumper::Useperl = 1;
314
315 # Set the TT template to use
316 $c->stash->{template} = 'books/create_done.tt2';
317 }
318
319
320=head2 Test Out The Form
321
322If the application is still running from before, use C<Ctrl-C> to kill
323it. Then restart the server:
324
325 $ script/myapp_server.pl
326
327Point your browser to L<http://localhost:3000/books/form_create> and
328enter "TCP/IP Illustrated, Vol 3" for the title, a rating of 5, and an
329author ID of 4. You should then be forwarded to the same
330C<create_done.tt2> template seen in earlier examples. Finally, click
331"Return to list" to view the full list of books.
332
333B<Note:> Having the user enter the primary key ID for the author is
334obviously crude; we will address this concern with a drop-down list in
3533daff 335Part 9.
d442cc9f 336
337
338=head1 A SIMPLE DELETE FEATURE
339
340Turning our attention to the delete portion of CRUD, this section
341illustrates some basic techniques that can be used to remove information
342from the database.
343
344
345=head2 Include a Delete Link in the List
346
347Edit C<root/src/books/list.tt2> and update it to the following (two
348sections have changed: 1) the additional '<th>Links</th>' table header,
349and 2) the four lines for the Delete link near the bottom).
350
351 [% # This is a TT comment. The '-' at the end "chomps" the newline. You won't -%]
352 [% # see this "chomping" in your browser because HTML ignores blank lines, but -%]
353 [% # it WILL eliminate a blank line if you view the HTML source. It's purely -%]
354 [%- # optional, but both the beginning and the ending TT tags support chomping. -%]
355
356 [% # Provide a title to root/lib/site/header -%]
357 [% META title = 'Book List' -%]
358
359 <table>
360 <tr><th>Title</th><th>Rating</th><th>Author(s)</th><th>Links</th></tr>
361 [% # Display each book in a table row %]
362 [% FOREACH book IN books -%]
363 <tr>
364 <td>[% book.title %]</td>
365 <td>[% book.rating %]</td>
366 <td>
367 [% # First initialize a TT variable to hold a list. Then use a TT FOREACH -%]
368 [% # loop in 'side effect notation' to load just the last names of the -%]
369 [% # authors into the list. Note that the 'push' TT vmethod does not -%]
370 [% # a value, so nothing will be printed here. But, if you have something -%]
371 [% # in TT that does return a method and you don't want it printed, you -%]
372 [% # can: 1) assign it to a bogus value, or 2) use the CALL keyword to -%]
373 [% # call it and discard the return value. -%]
374 [% tt_authors = [ ];
375 tt_authors.push(author.last_name) FOREACH author = book.authors %]
376 [% # Now use a TT 'virtual method' to display the author count in parens -%]
377 ([% tt_authors.size %])
378 [% # Use another TT vmethod to join & print the names & comma separators -%]
379 [% tt_authors.join(', ') %]
380 </td>
381 <td>
382 [% # Add a link to delete a book %]
383 <a href="[% Catalyst.uri_for('delete/') _ book.id %]">Delete</a>
384 </td>
385 </tr>
386 [% END -%]
387 </table>
388
389The additional code is obviously designed to add a new column to the
390right side of the table with a C<Delete> "button" (for simplicity, links
391will be used instead of full HTML buttons).
392
393=head2 Add a Delete Action to the Controller
394
395Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
396following method:
397
398 =head2 delete
399
400 Delete a book
401
402 =cut
403
404 sub delete : Local {
405 # $id = primary key of book to delete
406 my ($self, $c, $id) = @_;
407
408 # Search for the book and then delete it
d0496197 409 $c->model('DB::Books')->search({id => $id})->delete_all;
d442cc9f 410
411 # Set a status message to be displayed at the top of the view
412 $c->stash->{status_msg} = "Book deleted.";
413
414 # Forward to the list action/method in this controller
415 $c->forward('list');
416 }
417
418This method first deletes the book with the specified primary key ID.
419However, it also removes the corresponding entry from the
420C<book_authors> table. Note that C<delete_all> was used instead of
421C<delete>: whereas C<delete_all> also removes the join table entries in
422C<book_authors>, C<delete> does not (only use C<delete_all> if you
423really need the cascading deletes... otherwise you are wasting resources).
424
425Then, rather than forwarding to a "delete done" page as we did with the
426earlier create example, it simply sets the C<status_msg> to display a
427notification to the user as the normal list view is rendered.
428
429The C<delete> action uses the context C<forward> method to return the
430user to the book list. The C<detach> method could have also been used.
431Whereas C<forward> I<returns> to the original action once it is
432completed, C<detach> does I<not> return. Other than that, the two are
433equivalent.
434
435
436=head2 Try the Delete Feature
437
438If the application is still running from before, use C<Ctrl-C> to kill
439it. Then restart the server:
440
441 $ script/myapp_server.pl
442
443Then point your browser to L<http://localhost:3000/books/list> and click
444the "Delete" link next to the first "TCPIP_Illustrated_Vol-2". A green
445"Book deleted" status message should display at the top of the page,
446along with a list of the eight remaining books.
447
448
449=head2 Fixing a Dangerous URL
450
5edc2aae 451Note the URL in your browser once you have performed the deletion in the
d442cc9f 452prior step -- it is still referencing the delete action:
453
454 http://localhost:3000/books/delete/6
455
456What if the user were to press reload with this URL still active? In
457this case the redundant delete is harmless, but in other cases this
458could clearly be extremely dangerous.
459
460We can improve the logic by converting to a redirect. Unlike
461C<$c-E<gt>forward('list'))> or C<$c-E<gt>detach('list'))> that perform
462a server-side alteration in the flow of processing, a redirect is a
3533daff 463client-side mechanism that causes the browser to issue an entirely
d442cc9f 464new request. As a result, the URL in the browser is updated to match
465the destination of the redirection URL.
466
467To convert the forward used in the previous section to a redirect,
468open C<lib/MyApp/Controller/Books.pm> and edit the existing
469C<sub delete> method to match:
470
471 =head2 delete
472
473 Delete a book
474
475 =cut
476
477 sub delete : Local {
478 # $id = primary key of book to delete
479 my ($self, $c, $id) = @_;
480
481 # Search for the book and then delete it
d0496197 482 $c->model('DB::Books')->search({id => $id})->delete_all;
d442cc9f 483
484 # Set a status message to be displayed at the top of the view
485 $c->stash->{status_msg} = "Book deleted.";
486
487 # Redirect the user back to the list page
488 $c->response->redirect($c->uri_for('/books/list'));
489 }
490
491
492=head2 Try the Delete and Redirect Logic
493
494Restart the development server and point your browser to
3533daff 495L<http://localhost:3000/books/list> and delete the first copy of
496"TCPIP_Illustrated_Vol-2". The URL in your browser should return to
497the L<http://localhost:3000/books/list> URL, so that is an
498improvement, but notice that I<no green "Book deleted" status message
499is displayed>. Because the stash is reset on every request (and a
500redirect involves a second request), the C<status_msg> is cleared
501before it can be displayed.
d442cc9f 502
503
504=head2 Using C<uri_for> to Pass Query Parameters
505
506There are several ways to pass information across a redirect.
507In general, the best option is to use the C<flash> technique that we
3533daff 508will see in Part 5 of the tutorial; however, here we will pass the
d442cc9f 509information via query parameters on the redirect itself. Open
510C<lib/MyApp/Controller/Books.pm> and update the existing
511C<sub delete> method to match the following:
512
513 =head2 delete
514
515 Delete a book
516
517 =cut
518
519 sub delete : Local {
520 # $id = primary key of book to delete
521 my ($self, $c, $id) = @_;
522
523 # Search for the book and then delete it
d0496197 524 $c->model('DB::Books')->search({id => $id})->delete_all;
d442cc9f 525
526 # Redirect the user back to the list page with status msg as an arg
527 $c->response->redirect($c->uri_for('/books/list',
528 {status_msg => "Book deleted."}));
529 }
530
531This modification simply leverages the ability of C<uri_for> to include
532an arbitrary number of name/value pairs in a hash reference. Next, we
533need to update C<root/lib/site/layout> to handle C<status_msg> as a
534query parameter:
535
536 <div id="header">[% PROCESS site/header %]</div>
537
538 <div id="content">
539 <span class="message">[% status_msg || Catalyst.request.params.status_msg %]</span>
540 <span class="error">[% error_msg %]</span>
541 [% content %]
542 </div>
543
544 <div id="footer">[% PROCESS site/footer %]</div>
545
546
547=head2 Try the Delete and Redirect With Query Param Logic
548
549Restart the development server and point your browser to
550L<http://localhost:3000/books/list>. Then delete the remaining copy
551of "TCPIP_Illustrated_Vol-2". The green "Book deleted" status message
552should return.
553
554B<NOTE:> Although this did present an opportunity to show a handy
555capability of C<uri_for>, it would be much better to use Catalyst's
556C<flash> feature in this situation. Although the technique here is
557less dangerous than leaving the delete URL in the client's browser,
558we have still exposed the status message to the user. With C<flash>,
559this message returns to its rightful place as a service-side
560mechanism (we will migrate this code to C<flash> in the next part
561of the tutorial).
562
563
564=head1 AUTHOR
565
566Kennedy Clark, C<hkclark@gmail.com>
567
568Please report any errors, issues or suggestions to the author. The
569most recent version of the Catalyst Tutorial can be found at
d712b826 570L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Manual/lib/Catalyst/Manual/Tutorial/>.
d442cc9f 571
572Copyright 2006, Kennedy Clark, under Creative Commons License
573(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
574