Catalyst::Manual is now in the correct state.
[catagits/Catalyst-Manual.git] / lib / Catalyst / Manual / Tutorial / Tutorial / BasicCRUD.pod
diff --git a/lib/Catalyst/Manual/Tutorial/Tutorial/BasicCRUD.pod b/lib/Catalyst/Manual/Tutorial/Tutorial/BasicCRUD.pod
deleted file mode 100644 (file)
index 8c524c7..0000000
+++ /dev/null
@@ -1,568 +0,0 @@
-=head1 NAME
-
-Catalyst::Manual::Tutorial::BasicCRUD - Catalyst Tutorial - Part 3: Basic CRUD
-
-
-=head1 OVERVIEW
-
-This is B<Part 3 of 9> for the Catalyst tutorial.
-
-L<Tutorial Overview|Catalyst::Manual::Tutorial>
-
-=over 4
-
-=item 1
-
-L<Introduction|Catalyst::Manual::Tutorial::Intro>
-
-=item 2
-
-L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
-
-=item 3
-
-B<Basic CRUD>
-
-=item 4
-
-L<Authentication|Catalyst::Manual::Tutorial::Authentication>
-
-=item 5
-
-L<Authorization|Catalyst::Manual::Tutorial::Authorization>
-
-=item 6
-
-L<Debugging|Catalyst::Manual::Tutorial::Debugging>
-
-=item 7
-
-L<Testing|Catalyst::Manual::Tutorial::Testing>
-
-=item 8
-
-L<AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
-
-=item 9
-
-L<Appendices|Catalyst::Manual::Tutorial::Appendices>
-
-=back
-
-
-
-=head1 DESCRIPTION
-
-This part of the tutorial builds on the fairly primitive application
-created in Part 2 to add basic support for Create, Read, Update, and
-Delete (CRUD) of C<Book> objects.  Note that the 'list' function in Part
-2 already implements the Read portion of CRUD (although Read normally
-refers to reading a single object; you could implement full read
-functionality using the techniques introduced below).  This section will
-focus on the Create and Delete aspects of CRUD.  More advanced
-capabilities, including full Update functionality, will be addressed in
-Part 8.
-
-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
-
-Our initial attempt at object creation will utilize the "URL arguments"
-feature of Catalyst (we will employ the more common form-based
-submission in the sections that follow).
-
-
-=head2 Include a Create Action in the Books Controller
-
-Edit C<lib/MyApp/Controller/Books.pm> and enter the following method:
-
-    =head2 url_create
-    
-    Create a book with the supplied title, rating, and author
-    
-    =cut
-    
-    sub url_create : Local {
-        # In addition to self & context, get the title, rating, & 
-        # 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
-            });
-        
-        # Add a record to the join table for this book, mapping to 
-        # appropriate author
-        $book->add_to_book_authors({author_id => $author_id});
-        # Note: Above is a shortcut for this:
-        # $book->create_related('book_authors', {author_id => $author_id});
-        
-        # Assign the Book object to the stash for display in the view
-        $c->stash->{book} = $book;
-    
-        # This is a hack to disable XSUB processing in Data::Dumper
-        # (it's used in the view).  This is a work-around for a bug in
-        # the interaction of some versions or Perl, Data::Dumper & DBIC.
-        # You won't need this if you aren't using Data::Dumper (or if
-        # you are running DBIC 0.06001 or greater), but adding it doesn't 
-        # hurt anything either.
-        $Data::Dumper::Useperl = 1;
-    
-        # Set the TT template to use
-        $c->stash->{template} = 'books/create_done.tt2';
-    }
-
-Notice that Catalyst takes "extra slash-separated information" from the
-URL and passes it as arguments in C<@_>.  The C<url_create> action then
-uses a simple call to the DBIC C<create> method to add the requested
-information to the database (with a separate call to
-C<add_to_book_authors> to update the join table).  As do virtually all
-controller methods (at least the ones that directly handle user input),
-it then sets the template that should handle this request.
-
-
-=head2 Include a Template for the C<url_create> Action:
-
-Edit C<root/src/books/create_done.tt2> and then enter:
-
-    [% # Use the TT Dumper plugin to Data::Dumper variables to the browser   -%]
-    [% # Not a good idea for production use, though. :-)  'Indent=1' is      -%]
-    [% # optional, but prevents "massive indenting" of deeply nested objects -%]
-    [% USE Dumper(Indent=1) -%]
-    
-    [% # 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.  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                                    -%]
-    [% # 'uri_for()' builds a full URI; e.g., 'http://localhost:3000/books/list' -%]
-    <p><a href="[% Catalyst.uri_for('/books/list') %]">Return to list</a></p>
-    
-    [% # Try out the TT Dumper (for development only!) -%]
-    <pre>
-    Dump of the 'book' variable:
-    [% Dumper.dump(book) %]
-    </pre>
-
-The TT C<USE> directive allows access to a variety of plugin modules (TT
-plugins, that is, not Catalyst plugins) to add extra functionality to
-the base TT capabilities.  Here, the plugin allows L<Data::Dumper>
-"pretty printing" of objects and variables.  Other than that, the rest
-of the code should be familiar from the examples in Part 2.
-
-B<IMPORTANT NOTE> As mentioned earlier, the C<MyApp::View::TT.pm> view
-class created by TTSite redefines the name used to access the Catalyst
-context object in TT templates from the usual C<c> to C<Catalyst>.
-
-=head2 Try the C<url_create> Feature
-
-If the application is still running from before, use C<Ctrl-C> to kill
-it. Then restart the server:
-
-    $ script/myapp_server.pl
-
-Note that new path for C</books/url_create> appears in the startup debug
-output.
-
-B<TIP>: You can use C<script/myapp_server.pl -r> to have the development
-server auto-detect changed files and reload itself (if your browser acts
-odd, you should also try throwing in a C<-k>).  If you make changes to
-the TT templates only, you do not need to reload the development server
-(only changes to "compiled code" such as Controller and Model C<.pm>
-files require a reload).
-
-Next, use your browser to enter the following URL:
-
-    http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4
-
-Your browser should display " Added book 'TCPIP_Illustrated_Vol-2' by
-'Stevens' with a rating of 5." along with a dump of the new book model
-object.  You should also see the following DBIC debug messages displayed
-in the development server log messages:
-
-    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
-
-Although the C<url_create> action in the previous step does begin to
-reveal the power and flexibility of both Catalyst and DBIC, it's
-obviously not a very realistic example of how users should be expected
-to enter data.  This section begins to address that concern.
-
-
-=head2 Add Method to Display The Form
-
-Edit C<lib/MyApp/Controller/Books.pm> and add the following method:
-
-    =head2 form_create
-    
-    Display form to collect information for book to create
-    
-    =cut
-    
-    sub form_create : Local {
-        my ($self, $c) = @_;
-    
-        # Set the TT template to use
-        $c->stash->{template} = 'books/form_create.tt2';
-    }
-
-This action simply invokes a view containing a book creation form.
-
-=head2 Add a Template for the Form
-
-Open C<root/src/books/form_create.tt2> in your editor and enter:
-
-    [% META title = 'Manual Form Book Create' -%]
-    
-    <form method="post" action="[% Catalyst.uri_for('form_create_do') %]">
-    <table>
-      <tr><td>Title:</td><td><input type="text" name="title"></td></tr>
-      <tr><td>Rating:</td><td><input type="text" name="rating"></td></tr>
-      <tr><td>Author ID:</td><td><input type="text" name="author_id"></td></tr>
-    </table>
-    <input type="submit" name="Submit" value="Submit">
-    </form>
-
-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 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 database:
-
-    =head2 form_create_do
-    
-    Take information from form and add to database
-    
-    =cut
-    
-    sub form_create_do : Local {
-        my ($self, $c) = @_;
-    
-        # Retrieve the values from the form
-        my $title     = $c->request->params->{title}     || 'N/A';
-        my $rating    = $c->request->params->{rating}    || 'N/A';
-        my $author_id = $c->request->params->{author_id} || '1';
-    
-        # Create the book
-        my $book = $c->model('MyAppDB::Book')->create({
-                title   => $title,
-                rating  => $rating,
-            });
-        # Handle relationship with author
-        $book->add_to_book_authors({author_id => $author_id});
-    
-        # Store new model object in stash
-        $c->stash->{book} = $book;
-    
-        # Avoid Data::Dumper issue mentioned earlier
-        # You can probably omit this    
-        $Data::Dumper::Useperl = 1;
-    
-        # Set the TT template to use
-        $c->stash->{template} = 'books/create_done.tt2';
-    }
-
-
-=head2 Test Out The Form
-
-If the application is still running from before, use C<Ctrl-C> to kill
-it.  Then restart the server:
-
-    $ script/myapp_server.pl
-
-Point your browser to L<http://localhost:3000/books/form_create> and
-enter "TCP/IP Illustrated, Vol 3" for the title, a rating of 5, and an
-author ID of 4.  You should then be forwarded to the same
-C<create_done.tt2> template seen in earlier examples.  Finally, click
-"Return to list" to view the full list of books.
-
-B<Note:> Having the user enter the primary key ID for the author is
-obviously crude; we will address this concern with a drop-down list in
-Part 8.
-
-
-=head1 A SIMPLE DELETE FEATURE
-
-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.
-
-
-=head2 Include a Delete Link in the List
-
-Edit C<root/src/books/list.tt2> and update it to the following (two
-sections have changed: 1) the additional '<th>Links</th>' table header,
-and 2) the four lines for the Delete link near the bottom).
-
-    [% # This is a TT comment.  The '-' at the end "chomps" the newline.  You won't -%]
-    [% # see this "chomping" in your browser because HTML ignores blank lines, but  -%]
-    [% # it WILL eliminate a blank line if you view the HTML source.  It's purely   -%]
-    [%- # optional, but both the beginning and the ending TT tags support chomping. -%]
-    
-    [% # Provide a title to root/lib/site/header -%]
-    [% META title = 'Book List' -%]
-    
-    <table>
-    <tr><th>Title</th><th>Rating</th><th>Author(s)</th><th>Links</th></tr>
-    [% # Display each book in a table row %]
-    [% FOREACH book IN books -%]
-      <tr>
-        <td>[% book.title %]</td>
-        <td>[% book.rating %]</td>
-        <td>
-          [% # 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 %]
-          <a href="[% Catalyst.uri_for('delete/') _ book.id %]">Delete</a>
-        </td>
-      </tr>
-    [% END -%]
-    </table>
-
-The additional code is obviously designed to add a new column to the
-right side of the table with a C<Delete> "button" (for simplicity, links
-will be used instead of full HTML buttons).
-
-=head2 Add a Delete Action to the Controller
-
-Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
-following method:
-
-    =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.";
-    
-        # Forward to the list action/method in this controller
-        $c->forward('list');
-    }
-
-This method first deletes the book with the specified primary key ID.
-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 (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
-notification to the user as the normal list view is rendered.
-
-The C<delete> action uses the context C<forward> method to return the
-user to the book list.  The C<detach> method could have also been used.
-Whereas C<forward> I<returns> to the original action once it is
-completed, C<detach> does I<not> return.  Other than that, the two are
-equivalent.
-
-
-=head2 Try the Delete Feature
-
-If the application is still running from before, use C<Ctrl-C> to kill
-it.  Then restart the server:
-
-    $ script/myapp_server.pl
-
-Then point your browser to L<http://localhost:3000/books/list> and click
-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.  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/>).
-