Commit | Line | Data |
4d583dd8 |
1 | =head1 NAME |
2 | |
64ccd8a8 |
3 | Catalyst::Manual::Tutorial::BasicCRUD - Catalyst Tutorial - Part 3: Basic CRUD |
4d583dd8 |
4 | |
5 | |
6 | =head1 OVERVIEW |
7 | |
8 | This is B<Part 3 of 9> for the Catalyst tutorial. |
9 | |
64ccd8a8 |
10 | L<Tutorial Overview|Catalyst::Manual::Tutorial> |
4d583dd8 |
11 | |
12 | =over 4 |
13 | |
14 | =item 1 |
15 | |
16 | L<Introduction|Catalyst::Manual::Tutorial::Intro> |
17 | |
18 | =item 2 |
19 | |
20 | L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics> |
21 | |
22 | =item 3 |
23 | |
24 | B<Basic CRUD> |
25 | |
26 | =item 4 |
27 | |
28 | L<Authentication|Catalyst::Manual::Tutorial::Authentication> |
29 | |
30 | =item 5 |
31 | |
32 | L<Authorization|Catalyst::Manual::Tutorial::Authorization> |
33 | |
34 | =item 6 |
35 | |
36 | L<Debugging|Catalyst::Manual::Tutorial::Debugging> |
37 | |
38 | =item 7 |
39 | |
40 | L<Testing|Catalyst::Manual::Tutorial::Testing> |
41 | |
42 | =item 8 |
43 | |
44 | L<AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD> |
45 | |
46 | =item 9 |
47 | |
48 | L<Appendicies|Catalyst::Manual::Tutorial::Appendicies> |
49 | |
50 | =back |
51 | |
52 | |
53 | |
54 | =head1 DESCRIPTION |
55 | |
64ccd8a8 |
56 | This part of the tutorial builds on the fairly primitive application |
57 | created in Part 2 to add basic support for Create, Read, Update, and |
58 | Delete (CRUD) of C<Book> objects. Note that the 'list' function in Part |
59 | 2 already implements the Read portion of Crud (although Read normally |
60 | refers to reading a single object; you could implement full read |
61 | functionality using the techniques introduced below). This section will |
62 | focus on the Create and Delete aspects of CRUD. More advanced |
63 | capabilities, including full Update functionality, will be addressed in |
64 | Part 8. |
4d583dd8 |
65 | |
64ccd8a8 |
66 | B<TIP>: Note that all of the code for this part of the tutorial can be |
67 | pulled from the Catalyst Subversion repository in one step with the |
68 | following command: |
4d583dd8 |
69 | |
70 | svn checkout http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial@### |
71 | IMPORTANT: Does not work yet. Will be completed for final version. |
72 | |
73 | |
74 | |
75 | =head1 FORMLESS SUBMISSION |
76 | |
64ccd8a8 |
77 | Our initial attempt at object creation will utilize the "URL arguments" |
78 | feature of Catalyst (we will employ the more common form-based |
79 | submission in the sections that follow). |
4d583dd8 |
80 | |
81 | |
82 | =head2 Include a Create Action in the Books Controller |
83 | |
84 | Edit C<lib/MyApp/Controller/Books.pm> and enter the following method: |
85 | |
86 | =head2 url_create |
87 | |
88 | Create a book with the supplied title, rating and author |
89 | |
90 | =cut |
91 | |
92 | sub url_create : Local { |
93 | # In addition to self & context, get the title, rating & author_id args |
94 | # from the URL. Note that Catalyst automatically puts extra information |
95 | # after the "/<controller_name>/<action_name/" 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 |
100 | my $book = $c->model('MyAppDB::Book')->create({ |
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 | |
64ccd8a8 |
126 | Notice that Catalyst takes "extra slash-separated information" from the |
127 | URL and passes it as arguments in C<@_>. The C<url_create> action then |
128 | uses a simple call to the DBIC C<create> method to add the requested |
129 | information to the database (with a separate call to |
130 | C<add_to_book_authors> to update the join table). As do virtually all |
131 | controller methods (at least the ones that directly handle user input), |
132 | it then sets the template that should handle this request. |
4d583dd8 |
133 | |
134 | |
135 | =head2 Include a Template for the C<url_create> Action: |
136 | |
137 | Edit 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 -%] |
145 | [% META title = 'Book Created' %] |
146 | |
147 | [% # Output information about the record that was added. Note use -%] |
148 | [% # of 'first' to only list the first author (if > 1 author). -%] |
149 | <p>Added book '[% book.title %]' by '[% book.authors.first.last_name %]' |
150 | with a rating of [% book.rating %].</p> |
151 | |
152 | [% # Provide a link back to the list page -%] |
153 | [% # 'uri_for()' builds a full URI; e.g., 'http://localhost:3000/books/list' -%] |
154 | <p><a href="[% Catalyst.uri_for('/books/list') %]">Return to list</a></p> |
155 | |
156 | [% # Try out the TT Dumper -%] |
157 | <pre> |
158 | Dump of the 'book' variable: |
159 | [% Dumper.dump(book) %] |
160 | </pre> |
161 | |
64ccd8a8 |
162 | The TT C<USE> directive allows access to a variety of plugin modules (we |
163 | are talking TT plugins here, not Catalyst plugins) to add extra |
164 | functionality to the base TT capabilities. Here, the plugin allows |
165 | L<Data::Dumper|Data::Dumper> "pretty printing" of objects and variables. |
166 | Other than that, the rest of the code should be familiar from the |
167 | examples in Part 2. |
4d583dd8 |
168 | |
64ccd8a8 |
169 | B<IMPORTANT NOTE> As mentioned earlier, the C<MyApp::View::TT.pm> view |
170 | class created by TTSite redefines the name used to access the Catalyst |
171 | context object in TT templates from the usual C<c> to C<Catalyst>. |
4d583dd8 |
172 | |
173 | |
174 | =head2 Try the C<url_create> Feature |
175 | |
64ccd8a8 |
176 | If the application is still running from before, use C<Ctrl-C> to kill |
177 | it. Then restart the server: |
4d583dd8 |
178 | |
179 | $ script/myapp_server.pl |
180 | |
64ccd8a8 |
181 | Note that new path for C</books/url_create> appears in the startup debug |
182 | output. |
4d583dd8 |
183 | |
64ccd8a8 |
184 | B<TIP>: You can use C<script/myapp_server.pl -r> to have the development |
185 | server auto-detect changed files and reload itself (if your browser acts |
186 | odd, you should also try throwing in a C<-k>). If you make changes to |
187 | just the TT templates, you do not need to reload the development server |
188 | (only changes to "compiled code" such as Controller and Model C<.pm> |
189 | files require a reload). |
4d583dd8 |
190 | |
191 | Next, use your browser to enter the following URL: |
192 | |
193 | http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4 |
194 | |
64ccd8a8 |
195 | Your browser should display " Added book 'TCPIP_Illustrated_Vol-2' by |
196 | 'Stevens' with a rating of 5." along with a dump of the new book model |
197 | object. You should also see the following DBIC debug messages displayed |
198 | in the development server log messages: |
4d583dd8 |
199 | |
200 | INSERT INTO books (rating, title) VALUES (?, ?): `5', `TCPIP_Illustrated_Vol-2' |
201 | INSERT INTO book_authors (author_id, book_id) VALUES (?, ?): `4', `6' |
202 | |
64ccd8a8 |
203 | If you then click the "Return to list" link, you should find that there |
204 | are now six books shown (if necessary, Shift-Reload your browser at the |
205 | C</books/list> page). |
4d583dd8 |
206 | |
207 | |
208 | |
209 | =head1 MANUALLY BUILDING A CREATE FORM |
210 | |
64ccd8a8 |
211 | Although the C<url_create> action in the previous step does begin to |
212 | reveal the power and flexibility of both Catalyst and DBIC, it's |
213 | obviously not a very realistic example of how users should be expected |
214 | to enter data. This section begins to address that concern. |
4d583dd8 |
215 | |
216 | |
217 | =head2 Add Method to Display The Form |
218 | |
219 | Edit C<lib/MyApp/Controller/Books.pm> and add the following method: |
220 | |
221 | =head2 form_create |
222 | |
223 | Display form to collect information for book to create |
224 | |
225 | =cut |
226 | |
227 | sub form_create : Local { |
228 | my ($self, $c) = @_; |
229 | |
230 | # Set the TT template to use |
231 | $c->stash->{template} = 'books/form_create.tt2'; |
232 | } |
233 | |
234 | This action merely invokes a view containing a book creation form. |
235 | |
236 | |
237 | =head2 Add a Template for the Form |
238 | |
239 | Open C<root/src/books/form_create.tt2> in your editor and enter: |
240 | |
241 | [% META title = 'Book Create' -%] |
242 | |
243 | <form method="post" action="[% Catalyst.uri_for('form_create_do') %]"> |
244 | <table> |
245 | <tr><td>Title:</td><td><input type="text" name="title"></td></tr> |
246 | <tr><td>Rating:</td><td><input type="text" name="rating"></td></tr> |
247 | <tr><td>Author ID:</td><td><input type="text" name="author_id"></td></tr> |
248 | </table> |
249 | <input type="submit" name="Submit" value="Submit"> |
250 | </form> |
251 | |
64ccd8a8 |
252 | Note that we have specified the target of the form data as |
253 | C<form_create_do>, the method created in the section that follows. |
4d583dd8 |
254 | |
255 | |
256 | =head2 Add Method to Process Form Values and Update Database |
257 | |
64ccd8a8 |
258 | Edit C<lib/MyApp/Controller/Books.pm> and add the following method to |
259 | save the form information to the databse: |
4d583dd8 |
260 | |
261 | =head2 form_create_do |
262 | |
263 | Take information from form and add to database |
264 | |
265 | =cut |
266 | |
267 | sub form_create_do : Local { |
268 | my ($self, $c) = @_; |
269 | |
270 | # Retrieve the values from the form |
271 | my $title = $c->request->params->{title} || 'N/A'; |
272 | my $rating = $c->request->params->{rating} || 'N/A'; |
273 | my $author_id = $c->request->params->{author_id} || '1'; |
274 | |
275 | # Create the book |
276 | my $book = $c->model('MyAppDB::Book')->create({ |
277 | title => $title, |
278 | rating => $rating, |
279 | }); |
280 | # Handle relationship with author |
281 | $book->add_to_book_authors({author_id => $author_id}); |
282 | |
283 | # Store new model object in stash |
284 | $c->stash->{book} = $book; |
285 | |
286 | # Avoid Data::Dumper issue mention earlier |
287 | # You can probably omit this |
288 | $Data::Dumper::Useperl = 1; |
289 | |
290 | # Set the TT template to use |
291 | $c->stash->{template} = 'books/create_done.tt2'; |
292 | } |
293 | |
294 | |
295 | =head2 Test Out The Form |
296 | |
297 | If the application is still running from before, use C<Ctrl-C> to kill it. Then restart the server: |
298 | |
299 | $ script/myapp_server.pl |
300 | |
64ccd8a8 |
301 | Point your browser to L<http://localhost:3000/books/form_create> and |
302 | enter "TCP/IP Illustrated, Vol 3" for the title, a rating of 5, and an |
303 | author ID of 4. You should then be forwarded to the same |
304 | C<create_done.tt2> template seen in earlier examples. Finally, click |
305 | "Return to list" to view the full list of books. |
4d583dd8 |
306 | |
64ccd8a8 |
307 | B<Note:> Having the user enter the primary key ID for the author is |
308 | obviously a bit crude; we will address this concern with a drop-down |
309 | list in Part 8. |
4d583dd8 |
310 | |
311 | |
312 | |
313 | =head1 A SIMPLE DELETE FEATURE |
314 | |
64ccd8a8 |
315 | Turning out attention to the delete portion of CRUD, this section |
316 | illustrates some basic techniques that can be used to remove information |
317 | from the database. |
4d583dd8 |
318 | |
319 | |
320 | =head2 Include a Delete Link in the List |
321 | |
64ccd8a8 |
322 | Edit C<root/src/books/list.tt2> and update it to the following (two |
323 | sections have changed: 1) the additional '<th>Links</th>' table header, |
324 | and 2) the four lines for the Delete link near the bottom). |
4d583dd8 |
325 | |
326 | [% # This is a TT comment. The '-' at the end "chomps" the newline. You won't -%] |
327 | [% # see this "chomping" in your browser because HTML ignores blank lines, but -%] |
328 | [% # it WILL eliminate a blank line if you view the HTML source. It's purely -%] |
329 | [%- # optional, but both the beginning and the ending TT tags support chomping. -%] |
330 | |
331 | [% # Provide a title to root/lib/site/header -%] |
332 | [% META title = 'Book List' -%] |
333 | |
334 | <table> |
335 | <tr><th>Title</th><th>Rating</th><th>Author(s)</th><th>Links</th></tr> |
336 | [% # Display each book in a table row %] |
337 | [% FOREACH book IN books -%] |
338 | <tr> |
339 | <td>[% book.title %]</td> |
340 | <td>[% book.rating %]</td> |
341 | <td> |
342 | [% # Print author count in parens. 'book.authors' uses the 'many_to_many' -%] |
343 | [% # relationship to retrieve all of the authors of a book. 'size' is a -%] |
344 | [% # TT VMethod to get the number of elements in a list. -%] |
345 | ([% book.authors.size %]) |
346 | [% # Use an alternate form of a FOREACH loop to display authors. -%] |
347 | [% # _ below is the TT string concatenation operator. -%] |
348 | [% author.last_name _' ' FOREACH author = book.authors %] |
349 | [% # Note: if many_to_many relationship not used in Authors.pm, you could -%] |
350 | [% # have used the following to 'walk' through the 'join table objects' -%] |
351 | [% # bk_author.author.last_name _' ' FOREACH bk_author = book.book_authors %] |
352 | </td> |
353 | <td> |
354 | [% # Add a link to delete a book %] |
355 | <a href="[% Catalyst.uri_for('delete/') _ book.id %]">Delete</a> |
356 | </td> |
357 | </tr> |
358 | [% END -%] |
359 | </table> |
360 | |
64ccd8a8 |
361 | The additional code is obviously designed to add a new column to the |
362 | right side of the table with a C<Delete> "button" (for simplicity, links |
363 | will be used instead of full HTML buttons). |
4d583dd8 |
364 | |
365 | |
366 | =head2 Add a Delete Action to the Controller |
367 | |
64ccd8a8 |
368 | Open C<lib/MyApp/Controller/Books.pm> in your editor and add the |
369 | following method: |
4d583dd8 |
370 | |
371 | =head2 Delete |
372 | |
373 | Delete a book |
374 | |
375 | =cut |
376 | |
377 | sub delete : Local { |
378 | # $id = primary key of book to delete |
379 | my ($self, $c, $id) = @_; |
380 | |
381 | # Search for the book and then delete it |
382 | $c->model('MyAppDB::Book')->search({id => $id})->delete_all; |
383 | |
384 | # Set a status message to be displayed at the top of the view |
385 | $c->stash->{status_msg} = "Book deleted."; |
386 | |
387 | # Forward to the list action/method in this controller |
388 | $c->forward('list'); |
389 | } |
390 | |
64ccd8a8 |
391 | This method first deletes the book with the specified primary key ID. |
392 | However, it also removes the corresponding entry from the |
393 | C<book_authors> table. Note that C<delete_all> was used instead of |
394 | C<delete>: whereas C<delete_all> also removes the join table entries in |
395 | C<book_authors>, C<delete> does not. |
4d583dd8 |
396 | |
64ccd8a8 |
397 | Then, rather than forwarding to a "delete done" page as we did with the |
398 | earlier create example, it simply sets the C<status_msg> to display a |
399 | notification to the user as the normal list view is rendered. |
4d583dd8 |
400 | |
64ccd8a8 |
401 | The C<delete> action uses the context C<forward> method to return the |
402 | user to the book list. The C<detach> method could have also been used. |
403 | Whereas C<forward> I<returns> to the original action once it is |
404 | completed, C<detach> does I<not> return. Other than that, the two are |
405 | equivalent. |
4d583dd8 |
406 | |
64ccd8a8 |
407 | Another alternative to C<forward> would be to use |
408 | C<$c-E<gt>response-E<gt>redirect($c-E<gt>uri_for('/books/list'))>. The |
409 | C<forward> and C<redirect> operations differ in several important |
410 | respects that stem from the fact that redirects cause the client browser |
411 | to issue an entirely new HTTP request. In doing so, this results in a |
412 | new URL showing in the browser window. And, because the stash |
413 | information is reset for every request, the "Book deleted" message would |
414 | not be displayed. |
4d583dd8 |
415 | |
416 | |
417 | =head2 Try the Delete Feature |
418 | |
64ccd8a8 |
419 | If the application is still running from before, use C<Ctrl-C> to kill |
420 | it. Then restart the server: |
4d583dd8 |
421 | |
422 | $ script/myapp_server.pl |
423 | |
64ccd8a8 |
424 | Then point your browser to L<http://localhost:3000/books/list> and click |
425 | the "Delete" link next to "TCPIP_Illustrated_Vol-2". A green "Book |
426 | deleted" status message should display at the top of the page, along |
427 | with a list of the six remaining books. |
4d583dd8 |
428 | |
429 | |
430 | =head1 AUTHOR |
431 | |
432 | Kennedy Clark, C<hkclark@gmail.com> |
433 | |
434 | Please report any errors, issues or suggestions to the author. |
435 | |
64ccd8a8 |
436 | Copyright 2006, Kennedy Clark, under Creative Commons License |
437 | (L<http://creativecommons.org/licenses/by-nc-sa/2.5/>). |
4d583dd8 |
438 | |
439 | Version: .94 |
440 | |