=item 9
-L<Appendicies|Catalyst::Manual::Tutorial::Appendicies>
+L<Appendices|Catalyst::Manual::Tutorial::Appendices>
=back
capabilities, including full Update functionality, will be addressed in
Part 8.
-B<TIP>: Note that all of the code for this part of the tutorial can be
-pulled from the Catalyst Subversion repository in one step with the
-following command:
-
- svn checkout http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial@###
- IMPORTANT: Does not work yet. Will be completed for final version.
-
+You can checkout the source code for this example from the catalyst
+subversion repository as per the instructions in
+L<Catalyst::Manual::Tutorial::Intro>
=head1 FORMLESS SUBMISSION
# author_id args from the URL. Note that Catalyst automatically
# puts extra information after the "/<controller_name>/<action_name/"
# into @_
-
my ($self, $c, $title, $rating, $author_id) = @_;
# Call create() on the book model object. Pass the table
# columns/field values we want to set as hash values
my $book = $c->model('MyAppDB::Book')->create({
- title => $title,
- rating => $rating
+ title => $title,
+ rating => $rating
});
# Add a record to the join table for this book, mapping to
[% # optional, but prevents "massive indenting" of deeply nested objects -%]
[% USE Dumper(Indent=1) -%]
- [% # Set the page title -%]
+ [% # Set the page title. META can 'go back' and set values in templates -%]
+ [% # that have been processed 'before' this template (here it's for -%]
+ [% # root/lib/site/html and root/lib/site/header). Note that META on -%]
+ [% # simple strings (e.g., no variable interpolation). -%]
[% META title = 'Book Created' %]
- [% # Output information about the record that was added. Note use -%]
- [% # of 'first' to only list the first author (if > 1 author). -%]
- <p>Added book '[% book.title %]' by '[% book.authors.first.last_name %]'
+ [% # Output information about the record that was added. First title. -%]
+ <p>Added book '[% book.title %]'
+
+ [% # Output the last name of the first author. This is complicated by an -%]
+ [% # issue in TT 2.15 where blessed hash objects are not handled right. -%]
+ [% # First, fetch 'book.authors' from the DB once. -%]
+ [% authors = book.authors %]
+ [% # Now use IF statements to test if 'authors.first' is "working". If so, -%]
+ [% # we use it. Otherwise we use a hack that seems to keep TT 2.15 happy. -%]
+ by '[% authors.first.last_name IF authors.first;
+ authors.list.first.value.last_name IF ! authors.first %]'
+
+ [% # Output the rating for the book that was added -%]
with a rating of [% book.rating %].</p>
[% # Provide a link back to the list page -%]
INSERT INTO books (rating, title) VALUES (?, ?): `5', `TCPIP_Illustrated_Vol-2'
INSERT INTO book_authors (author_id, book_id) VALUES (?, ?): `4', `6'
+ SELECT author.id, author.first_name, author.last_name
+ FROM book_authors me JOIN authors author
+ ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '6'
+
+The C<INSERT> statements are obviously adding the book and linking it to
+the existing record for Richard Stevens. The C<SELECT> statement results
+from DBIC automatically fetching the book for the C<Dumper.dump(book)>.
If you then click the "Return to list" link, you should find that there
are now six books shown (if necessary, Shift-Reload your browser at the
C</books/list> page).
+Then I<add 2 more copies of the same book> so that we have some extras for
+our delete logic that will be coming up soon. Enter the same URL above
+two more times (or refresh your browser twice if it still contains this
+URL):
+
+ http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4
+
+You should be able to click "Return to list" and now see 3 copies of
+"TCP_Illustrated_Vol-2".
+
=head1 MANUALLY BUILDING A CREATE FORM
Open C<root/src/books/form_create.tt2> in your editor and enter:
- [% META title = 'Book Create' -%]
+ [% META title = 'Manual Form Book Create' -%]
<form method="post" action="[% Catalyst.uri_for('form_create_do') %]">
<table>
Note that we have specified the target of the form data as
C<form_create_do>, the method created in the section that follows.
-=head2 Add Method to Process Form Values and Update Database
+=head2 Add a Method to Process Form Values and Update Database
Edit C<lib/MyApp/Controller/Books.pm> and add the following method to
-save the form information to the databse:
+save the form information to the database:
=head2 form_create_do
obviously crude; we will address this concern with a drop-down list in
Part 8.
+
=head1 A SIMPLE DELETE FEATURE
-Turning out attention to the delete portion of CRUD, this section
+Turning our attention to the delete portion of CRUD, this section
illustrates some basic techniques that can be used to remove information
from the database.
<td>[% book.title %]</td>
<td>[% book.rating %]</td>
<td>
- [% # Print author count in parens. 'book.authors' uses the 'many_to_many' -%]
- [% # relationship to retrieve all of the authors of a book. 'size' is a -%]
- [% # TT VMethod to get the number of elements in a list. -%]
- ([% book.authors.size %])
- [% # Use an alternate form of a FOREACH loop to display authors. -%]
- [% # _ below is the TT string concatenation operator. -%]
- [% author.last_name _' ' FOREACH author = book.authors %]
- [% # Note: if many_to_many relationship not used in Authors.pm, you could -%]
- [% # have used the following to 'walk' through the 'join table objects' -%]
- [% # bk_author.author.last_name _' ' FOREACH bk_author = book.book_authors %]
+ [% # First initialize a TT variable to hold a list. Then use a TT FOREACH -%]
+ [% # loop in 'side effect notation' to load just the last names of the -%]
+ [% # authors into the list. Note that the 'push' TT vmethod does not -%]
+ [% # a value, so nothing will be printed here. But, if you have something -%]
+ [% # in TT that does return a method and you don't want it printed, you -%]
+ [% # can: 1) assign it to a bogus value, or 2) use the CALL keyword to -%]
+ [% # call it and discard the return value. -%]
+ [% tt_authors = [ ];
+ tt_authors.push(author.last_name) FOREACH author = book.authors %]
+ [% # Now use a TT 'virtual method' to display the author count in parens -%]
+ ([% tt_authors.size %])
+ [% # Use another TT vmethod to join & print the names & comma separators -%]
+ [% tt_authors.join(', ') %]
</td>
<td>
[% # Add a link to delete a book %]
Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
following method:
- =head2 Delete
+ =head2 delete
Delete a book
However, it also removes the corresponding entry from the
C<book_authors> table. Note that C<delete_all> was used instead of
C<delete>: whereas C<delete_all> also removes the join table entries in
-C<book_authors>, C<delete> does not.
+C<book_authors>, C<delete> does not (only use C<delete_all> if you
+really need the cascading deletes... otherwise you are wasting resources).
Then, rather than forwarding to a "delete done" page as we did with the
earlier create example, it simply sets the C<status_msg> to display a
completed, C<detach> does I<not> return. Other than that, the two are
equivalent.
-Another alternative to C<forward> would be to use
-C<$c-E<gt>response-E<gt>redirect($c-E<gt>uri_for('/books/list'))>. The
-C<forward> and C<redirect> operations differ in several important
-respects that stem from the fact that redirects cause the client browser
-to issue an entirely new HTTP request. In doing so, this results in a
-new URL showing in the browser window. And, because the stash
-information is reset for every request, the "Book deleted" message would
-not be displayed.
-
=head2 Try the Delete Feature
$ script/myapp_server.pl
Then point your browser to L<http://localhost:3000/books/list> and click
-the "Delete" link next to "TCPIP_Illustrated_Vol-2". A green "Book
-deleted" status message should display at the top of the page, along
-with a list of the six remaining books.
+the "Delete" link next to the first "TCPIP_Illustrated_Vol-2". A green
+"Book deleted" status message should display at the top of the page,
+along with a list of the eight remaining books.
+
+
+=head2 Fixing a Dangerous URL
+
+Note the URL in your browser once you have performed the deletetion in the
+prior step -- it is still referencing the delete action:
+
+ http://localhost:3000/books/delete/6
+
+What if the user were to press reload with this URL still active? In
+this case the redundant delete is harmless, but in other cases this
+could clearly be extremely dangerous.
+
+We can improve the logic by converting to a redirect. Unlike
+C<$c-E<gt>forward('list'))> or C<$c-E<gt>detach('list'))> that perform
+a server-side alteration in the flow of processing, a redirect is a
+client-side mechanism that causes the brower to issue an entirely
+new request. As a result, the URL in the browser is updated to match
+the destination of the redirection URL.
+
+To convert the forward used in the previous section to a redirect,
+open C<lib/MyApp/Controller/Books.pm> and edit the existing
+C<sub delete> method to match:
+
+ =head2 delete
+
+ Delete a book
+
+ =cut
+
+ sub delete : Local {
+ # $id = primary key of book to delete
+ my ($self, $c, $id) = @_;
+
+ # Search for the book and then delete it
+ $c->model('MyAppDB::Book')->search({id => $id})->delete_all;
+
+ # Set a status message to be displayed at the top of the view
+ $c->stash->{status_msg} = "Book deleted.";
+
+ # Redirect the user back to the list page
+ $c->response->redirect($c->uri_for('/books/list'));
+ }
+
+
+=head2 Try the Delete and Redirect Logic
+
+Restart the development server and point your browser to
+L<http://localhost:3000/books/list>. Delete the first copy of
+"TCPIP_Illustrated_Vol-2", but notice that I<no green "Book deleted"
+status message is displayed>. Because the stash is reset on every
+request (and a redirect involves a second request), the
+C<status_msg> is cleared before it can be displayed.
+
+
+=head2 Using C<uri_for> to Pass Query Parameters
+
+There are several ways to pass information across a redirect.
+In general, the best option is to use the C<flash> technique that we
+will see in Part 4 of the tutorial; however, here we will pass the
+information via query parameters on the redirect itself. Open
+C<lib/MyApp/Controller/Books.pm> and update the existing
+C<sub delete> method to match the following:
+
+ =head2 delete
+
+ Delete a book
+
+ =cut
+
+ sub delete : Local {
+ # $id = primary key of book to delete
+ my ($self, $c, $id) = @_;
+
+ # Search for the book and then delete it
+ $c->model('MyAppDB::Book')->search({id => $id})->delete_all;
+
+ # Redirect the user back to the list page with status msg as an arg
+ $c->response->redirect($c->uri_for('/books/list',
+ {status_msg => "Book deleted."}));
+ }
+
+This modification simply leverages the ability of C<uri_for> to include
+an arbitrary number of name/value pairs in a hash reference. Next, we
+need to update C<root/lib/site/layout> to handle C<status_msg> as a
+query parameter:
+
+ <div id="header">[% PROCESS site/header %]</div>
+
+ <div id="content">
+ <span class="message">[% status_msg || Catalyst.request.params.status_msg %]</span>
+ <span class="error">[% error_msg %]</span>
+ [% content %]
+ </div>
+
+ <div id="footer">[% PROCESS site/footer %]</div>
+
+
+=head2 Try the Delete and Redirect With Query Param Logic
+
+Restart the development server and point your browser to
+L<http://localhost:3000/books/list>. Then delete the remaining copy
+of "TCPIP_Illustrated_Vol-2". The green "Book deleted" status message
+should return.
+
+B<NOTE:> Although this did present an opportunity to show a handy
+capability of C<uri_for>, it would be much better to use Catalyst's
+C<flash> feature in this situation. Although the technique here is
+less dangerous than leaving the delete URL in the client's browser,
+we have still exposed the status message to the user. With C<flash>,
+this message returns to its rightful place as a service-side
+mechanism (we will migrate this code to C<flash> in the next part
+of the tutorial).
+
=head1 AUTHOR
Kennedy Clark, C<hkclark@gmail.com>
-Please report any errors, issues or suggestions to the author.
+Please report any errors, issues or suggestions to the author. The
+most recent version of the Catalyst Tutorial can be found at
+L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Runtime/lib/Catalyst/Manual/Tutorial/>.
Copyright 2006, Kennedy Clark, under Creative Commons License
(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
-Version: .94
-