From: Kennedy Clark Date: Mon, 23 Feb 2009 22:07:30 +0000 (+0000) Subject: Misc updates to factor out find()'ing an object into a common 'object' method and... X-Git-Tag: v5.8005~208 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Manual.git;a=commitdiff_plain;h=994b66ad3ec2a66fb5d22957474229422b9ce110;hp=8a0214b4275e310f64f74c2a250a20c01efd4c66 Misc updates to factor out find()'ing an object into a common 'object' method and store the resultset inside 'base' --- diff --git a/lib/Catalyst/Manual/Tutorial/BasicCRUD.pod b/lib/Catalyst/Manual/Tutorial/BasicCRUD.pod index f2565f2..0aa7e2c 100644 --- a/lib/Catalyst/Manual/Tutorial/BasicCRUD.pod +++ b/lib/Catalyst/Manual/Tutorial/BasicCRUD.pod @@ -390,7 +390,7 @@ object. Click the "Return to list" link, you should find that there are now seven books shown (two copies of TCPIP_Illustrated_Vol-2). -=head2 Refactor to Use a "Base" Method to Start The Chains +=head2 Refactor to Use a "Base" Method to Start the Chains Let's make a quick update to our initial Chained action to show a little more of the power of chaining. First, open @@ -405,20 +405,28 @@ method: sub base :Chained('/') :PathPart('books') :CaptureArgs(0) { my ($self, $c) = @_; + + # Store the resultset in stash so it's available for other methods + $c->stash->{resultset} = $c->model('DB::Books'); + # Print a message to the debug log $c->log->debug('*** INSIDE BASE METHOD ***'); } -Although we only use the C method to create a log message, we -could obviously do any number of things here. For example, if your -controller always needs a book ID as it's first argument, you could -have the base method capture that argument (with C<:CaptureArgs(1)>) -and use it to pull the book object with that ID from the database and -leave it in the stash for later parts of your chains to then act upon. - -In our case, let's modify our C method to first call -C. Open up C and edit the -declaration for C to match the following: +Here we print a log message and store the resultset in +C<$c-Estash-E{resultset}> so that it's automatically available +for other actions that chain off C. If your controller always +needs a book ID as it's first argument, you could have the base method +capture that argument (with C<:CaptureArgs(1)>) and use it to pull the +book object with that ID from the database and leave it in the stash for +later parts of your chains to then act upon. Because we have several +actions that don't need to retrieve a book (such as the C +we are working with now), we will instead add that functionality +to a common C action shortly. + +As for C, let's modify it to first dispatch to C. +Open up C and edit the declaration for +C to match the following: sub url_create :Chained('base') :PathPart('url_create') :Args(3) { @@ -612,7 +620,7 @@ and 2) the four lines for the Delete link near the bottom). [% # Add a link to delete a book %] - Delete + Delete [% END -%] @@ -631,6 +639,55 @@ operations that change the state of your application (e.g., the database). +=head2 Add a Common Method to Retrieve a Book for the Chain + +As mentioned earlier, since we have a mixture of actions that operate on +a single book ID and others that do no, we should not have C +capture the book ID, find the corresponding book in the database and +save it in the stash for later links in the chain. However, just +because that logic does not belong in C doesn't mean that we can't +create another location to centralize that logic. In our case, we will +create a method called C that will store the specific book in +the stash. Chains that always operate on a single existing book can +chain off this method, but methods such as C that don't +operate on an existing book can chain directly off base. + +To add the C method, edit C +and add the following code: + + sub object :Chained('base') :PathPart('id') :CaptureArgs(1) { + my ($self, $c, $id) = @_; + + # Find the book object and store it in the stash + $c->stash(object => $c->stash->{resultset}->find($id)); + + # Make sure the lookup was successful. You would probably + # want to do something like this in a real app: + # $c->detach('/error_404') if !$c->stash->{object}; + die "Book $id not found!" if !$c->stash->{object}; + } + +Now, any other method that chains off C will automatically +have the appropriate book waiting for it in +C<$c-Estash-Egt>{object}>. + +Also note that we are using different technique for setting +C<$c-Estash>. The advantage of this style is that it let's you +set multiple stash variables at a time. For example: + + $c->stash(object => $c->stash->{resultset}->find($id), + another_thing => 1); + +or as a hashref: + + $c->stash({object => $c->stash->{resultset}->find($id), + another_thing => 1}); + +Either format works, but the C<$c-Estash(name => value);> +style is growing in popularity -- you may which to use it all +the time (even when you are only setting a single value). + + =head2 Add a Delete Action to the Controller Open C in your editor and add the @@ -642,12 +699,13 @@ following method: =cut - sub delete :Chained('base') :PathPart('delete') :Args(1) { + sub delete :Chained('object') :PathPart('delete') :Args(0) { # $id = primary key of book to delete - my ($self, $c, $id) = @_; + my ($self, $c) = @_; - # Search for the book and then delete it - $c->model('DB::Books')->search({id => $id})->delete_all; + # Use the book object saved by 'object' and delete it along + # with related 'book_authors' entries + $c->stash->{object}->delete; # Set a status message to be displayed at the top of the view $c->stash->{status_msg} = "Book deleted."; @@ -656,12 +714,10 @@ following method: $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 table. Note that C was used instead of -C: whereas C also removes the join table entries in -C, C does not (only use C if you -really need the cascading deletes... otherwise you are wasting resources). +This method first deletes the book object saved by the C method. +However, it also removes the corresponding entry from the +C table. Note that C will cascade to also delete +the related join table entries in C. Then, rather than forwarding to a "delete done" page as we did with the earlier create example, it simply sets the C to display a @@ -679,29 +735,35 @@ equivalent. If the application is still running from before, use C to kill it. Then restart the server: - $ script/myapp_server.pl + $ DBIC_TRACE=1 script/myapp_server.pl The C method now appears in the "Loaded Chained actions" section of the startup debug output: [debug] Loaded Chained actions: - .-------------------------------------+--------------------------------------. - | Path Spec | Private | - +-------------------------------------+--------------------------------------+ - | /books/delete/* | /books/base (0) | - | | => /books/delete | - | /books/form_create | /books/base (0) | - | | => /books/form_create | - | /books/form_create_do | /books/base (0) | - | | => /books/form_create_do | - | /books/url_create/*/*/* | /books/base (0) | - | | => /books/url_create | - '-------------------------------------+--------------------------------------' + .-------------------------------------+--------------------------------------. + | Path Spec | Private | + +-------------------------------------+--------------------------------------+ + | /books/id/*/delete | /books/base (0) | + | | -> /books/object (1) | + | | => /books/delete | + | /books/form_create | /books/base (0) | + | | => /books/form_create | + | /books/form_create_do | /books/base (0) | + | | => /books/form_create_do | + | /books/url_create/*/*/* | /books/base (0) | + | | => /books/url_create | + '-------------------------------------+--------------------------------------' Then point your browser to L 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. +along with a list of the eight remaining books. You will also see the +cascading delete operation via the DBIC_TRACE output: + + DELETE FROM books WHERE ( id = ? ): '6' + SELECT me.book_id, me.author_id FROM book_authors me WHERE ( me.book_id = ? ): '6' + DELETE FROM book_authors WHERE ( author_id = ? AND book_id = ? ): '4', '6' =head2 Fixing a Dangerous URL @@ -726,18 +788,19 @@ To convert the forward used in the previous section to a redirect, open C and edit the existing C method to match: - =head2 delete + =head2 delete Delete a book - + =cut - sub delete :Chained('base') :PathPart('delete') :Args(1) { + sub delete :Chained('object') :PathPart('delete') :Args(0) { # $id = primary key of book to delete my ($self, $c, $id) = @_; - # Search for the book and then delete it - $c->model('DB::Books')->search({id => $id})->delete_all; + # Use the book object saved by 'object' and delete it along + # with related 'book_authors' entries + $c->stash->{object}->delete; # Set a status message to be displayed at the top of the view $c->stash->{status_msg} = "Book deleted."; @@ -750,13 +813,15 @@ C method to match: =head2 Try the Delete and Redirect Logic Restart the development server and point your browser to -L and delete the first copy of the -remaining two "TCPIP_Illustrated_Vol-2" books. The URL in your -browser should return to the L URL, -so that is an improvement, but notice that I. Because the stash is reset on every -request (and a redirect involves a second request), the C -is cleared before it can be displayed. +L (don't just hit "Refresh" in your +browser since we left the URL in an invalid state in the previous +section!) and delete the first copy of the remaining two +"TCPIP_Illustrated_Vol-2" books. The URL in your browser should return +to the L URL, so that is an +improvement, but notice that I. Because the stash is reset on every request (and a redirect +involves a second request), the C is cleared before it can +be displayed. =head2 Using C to Pass Query Parameters @@ -774,12 +839,13 @@ method to match the following: =cut - sub delete :Chained('base') :PathPart('delete') :Args(1) { + sub delete :Chained('object') :PathPart('delete') :Args(0) { # $id = primary key of book to delete my ($self, $c, $id) = @_; - # Search for the book and then delete it - $c->model('DB::Books')->search({id => $id})->delete_all; + # Use the book object saved by 'object' and delete it along + # with related 'book_authors' entries + $c->stash->{object}->delete; # Redirect the user back to the list page with status msg as an arg $c->response->redirect($c->uri_for('/books/list', @@ -810,8 +876,9 @@ Cspan class="message"E> line. =head2 Try the Delete and Redirect With Query Param Logic Restart the development server and point your browser to -L. Then delete the remaining copy -of "TCPIP_Illustrated_Vol-2". The green "Book deleted" status message +L (you should now be able to safely +hit "refresh" in your browser). Then delete the remaining copy of +"TCPIP_Illustrated_Vol-2". The green "Book deleted" status message should return. B Another popular method for maintaining server-side @@ -822,10 +889,10 @@ C is a "slicker" mechanism in that it's all handled by the server and doesn't "pollute" your URLs, B can lead to situations where the wrong information shows up in the wrong browser window if the user has multiple windows or -browser tabs open.> (For example, Window A causes something to be +browser tabs open.> For example, Window A causes something to be placed in the stash, but before that window performs a redirect, Window B makes a request to the server and gets the status information -that should really go to Window A.) For this reason, you may wish +that should really go to Window A. For this reason, you may wish to use the "query param" technique shown here in your applications.