X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Manual.git;a=blobdiff_plain;f=lib%2FCatalyst%2FManual%2FTutorial%2FAuthentication.pod;h=826702594a919dc8dce237ec96bedc5fbd182e91;hp=1c3b9b84d65fd04ec6c2153635baf1e1bf5a0bb8;hb=7e8cd0091d6a251c847dc3abd269f864af0f9d50;hpb=ae4928627383baa200c4f20f5237e423f5409b12 diff --git a/lib/Catalyst/Manual/Tutorial/Authentication.pod b/lib/Catalyst/Manual/Tutorial/Authentication.pod index 1c3b9b8..8267025 100644 --- a/lib/Catalyst/Manual/Tutorial/Authentication.pod +++ b/lib/Catalyst/Manual/Tutorial/Authentication.pod @@ -1,11 +1,11 @@ =head1 NAME -Catalyst::Manual::Tutorial::Authentication - Catalyst Tutorial - Part 5: Authentication +Catalyst::Manual::Tutorial::Authentication - Catalyst Tutorial - Chapter 5: Authentication =head1 OVERVIEW -This is B for the Catalyst tutorial. +This is B for the Catalyst tutorial. L @@ -56,16 +56,17 @@ L =head1 DESCRIPTION -Now that we finally have a simple yet functional application, we can -focus on providing authentication (with authorization coming next in -Part 5). +Now that we finally have a simple yet functional application, we can +focus on providing authentication (with authorization coming next in +Chapter 6). -This part of the tutorial is divided into two main sections: 1) basic, +This chapter of the tutorial is divided into two main sections: 1) basic, cleartext authentication and 2) hash-based authentication. You can checkout the source code for this example from the catalyst subversion repository as per the instructions in -L +L. + =head1 BASIC AUTHENTICATION @@ -77,7 +78,7 @@ application. First, we add both user and role information to the database (we will add the role information here although it will not be used until the -authorization section, Part 5). Create a new SQL script file by opening +authorization section, Chapter 6). Create a new SQL script file by opening C in your editor and insert: -- @@ -125,96 +126,102 @@ Although we could manually edit the DBIC schema information to include the new tables added in the previous step, let's use the C option on the DBIC model helper to do most of the work for us: - $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema create=static dbi:SQLite:myapp.db - $ ls lib/MyApp/Schema + $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \ + create=static components=TimeStamp dbi:SQLite:myapp.db + exists "/root/dev/MyApp/script/../lib/MyApp/Model" + exists "/root/dev/MyApp/script/../t" + Dumping manual schema for MyApp::Schema to directory /root/dev/MyApp/script/../lib ... + Schema dump completed. + exists "/root/dev/MyApp/script/../lib/MyApp/Model/DB.pm" + $ + $ ls lib/MyApp/Schema/Result Authors.pm BookAuthors.pm Books.pm Roles.pm UserRoles.pm Users.pm -Notice how the helper has added three new table-specific result source -files to the C directory. And, more -importantly, even if there were changes to the existing result source -files, those changes would have only been written above the C<# DO NOT -MODIFY THIS OR ANYTHING ABOVE!> comment and your hand-editted +Notice how the helper has added three new table-specific result source +files to the C directory. And, more +importantly, even if there were changes to the existing result source +files, those changes would have only been written above the C<# DO NOT +MODIFY THIS OR ANYTHING ABOVE!> comment and your hand-edited enhancements would have been preserved. - -Speaking of "hand-editted enhancements," we should now add -relationship information to the three new result source files. Edit -each of these files and add the following information between the C<# +Speaking of "hand-edit ted enhancements," we should now add +relationship information to the three new result source files. Edit +each of these files and add the following information between the C<# DO NOT MODIFY THIS OR ANYTHING ABOVE!> comment and the closing C<1;>: -C: +C: # # Set relationships: # - + # has_many(): # args: # 1) Name of relationship, DBIC will create accessor with this name # 2) Name of the model class referenced by this relationship - # 3) Column name in *foreign* table - __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::UserRoles', 'user_id'); - + # 3) Column name in *foreign* table (aka, foreign key in peer table) + __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::Result::UserRoles', 'user_id'); + # many_to_many(): # args: # 1) Name of relationship, DBIC will create accessor with this name - # 2) Name of has_many() relationship this many_to_many() is shortcut for - # 3) Name of belongs_to() relationship in model class of has_many() above + # 2) Name of has_many() relationship this many_to_many() is shortcut for + # 3) Name of belongs_to() relationship in model class of has_many() above # You must already have the has_many() defined to use a many_to_many(). __PACKAGE__->many_to_many(roles => 'map_user_role', 'role'); -C: +C: # # Set relationships: # - + # has_many(): # args: # 1) Name of relationship, DBIC will create accessor with this name # 2) Name of the model class referenced by this relationship - # 3) Column name in *foreign* table - __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::UserRoles', 'role_id'); + # 3) Column name in *foreign* table (aka, foreign key in peer table) + __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::Result::UserRoles', 'role_id'); -C: +C: # # Set relationships: # - + # belongs_to(): # args: # 1) Name of relationship, DBIC will create accessor with this name # 2) Name of the model class referenced by this relationship # 3) Column name in *this* table - __PACKAGE__->belongs_to(user => 'MyApp::Schema::Users', 'user_id'); - + __PACKAGE__->belongs_to(user => 'MyApp::Schema::Result::Users', 'user_id'); + # belongs_to(): # args: # 1) Name of relationship, DBIC will create accessor with this name # 2) Name of the model class referenced by this relationship # 3) Column name in *this* table - __PACKAGE__->belongs_to(role => 'MyApp::Schema::Roles', 'role_id'); + __PACKAGE__->belongs_to(role => 'MyApp::Schema::Result::Roles', 'role_id'); -The code for these three sets of updates is obviously very similar to -the edits we made to the C, C, and C -classes created in Part 3. +The code for these three sets of updates is obviously very similar to +the edits we made to the C, C, and C +classes created in Chapter 3. -Note that we do not need to make any change to the -C schema file. It simple tells DBIC to -load all of the result source files it finds in below the -C directory, so it will automatically pick -up our new table information. +Note that we do not need to make any change to the +C schema file. It simply tells DBIC to load all +of the Result Class and ResultSet Class files it finds in below the +C directory, so it will automatically pick up our +new table information. =head2 Sanity-Check Reload of Development Server -We aren't ready to try out the authentication just yet; we only want -to do a quick check to be sure our model loads correctly. Press -C to kill the previous server instance (if it's still running) +We aren't ready to try out the authentication just yet; we only want +to do a quick check to be sure our model loads correctly. Press +C to kill the previous server instance (if it's still running) and restart it: $ script/myapp_server.pl @@ -238,102 +245,112 @@ Look for the three new model objects in the startup debug output: '-------------------------------------------------------------------+----------' ... -Again, notice that your "result source" classes have been "re-loaded" +Again, notice that your "Result Class" classes have been "re-loaded" by Catalyst under C. =head2 Include Authentication and Session Plugins -Edit C and update it as follows (everything below +Edit C and update it as follows (everything below C is new): - use Catalyst qw/ - -Debug - ConfigLoader - Static::Simple - - StackTrace - - Authentication - - Session - Session::Store::FastMmap - Session::State::Cookie - /; - -The C plugin supports Authentication while the -C plugins are required to maintain state across multiple HTTP -requests. - -Note that the only required Authentication class is the main one. This -is a change that occurred in version 0.09999_01 of the -C plugin. You B to specify a particular -Authentication::Store or Authentication::Credential plugin. Instead, -indicate the Store and Credential you want to use in your application + # Load plugins + use Catalyst qw/-Debug + ConfigLoader + Static::Simple + + StackTrace + + Authentication + + Session + Session::Store::FastMmap + Session::State::Cookie + /; + +B As discussed in MoreCatalystBasics, different versions of +C have used a variety of methods to load the plugins. +You can put the plugins in the C statement if you prefer. + +The C plugin supports Authentication while the +C plugins are required to maintain state across multiple HTTP +requests. + +Note that the only required Authentication class is the main one. This +is a change that occurred in version 0.09999_01 of the +C plugin. You B to specify a particular +Authentication::Store or Authentication::Credential plugin. Instead, +indicate the Store and Credential you want to use in your application configuration (see below). -Note that there are several options for -L -(L -is generally a good choice if you are on Unix; try -L if you -are on Win32) -- consult -L and its subclasses +Note that there are several options for +L +(L +is generally a good choice if you are on Unix; try +L if you +are on Win32) -- consult +L and its subclasses for additional information and options (for example to use a database- backed session store). =head2 Configure Authentication -Although C<__PACKAGE__-Econfig(name =E 'value');> is still -supported, newer Catalyst applications tend to place all configuration -information in C and automatically load this information -into Cconfig> using the -L plugin. - -First, as noted in Part 3 of the tutorial, Catalyst has recently -switched from a default config file format of YAML to -C (an apache-like format). In case you are using -a version of Catalyst earlier than v5.7014, delete the C -file and simply follow the directions below to create a new -C file. - -Here, we need to load several parameters that tell -L -where to locate information in your database. To do this, edit the +Although C<__PACKAGE__-Econfig(name =E 'value');> is still +supported, newer Catalyst applications tend to place all configuration +information in C and automatically load this information +into Cconfig> using the +L plugin. + +As discussed in Chapter 3 of the tutorial, Catalyst has recently +switched from a default config file format of YAML to +L (an apache-like format). In case +you are using a version of Catalyst earlier than v5.7014, delete the +C, or convert it to .conf format using the TIP in +L +then simply follow the directions below to create a new C +file. Although we will use the C format here because +YAML files can be difficult to cut and paste in certain environments, +you are free to use any format supported by +L and +L -- Catalyst will transparently handle the +different formats. + +Here, we need to load several parameters that tell +L +where to locate information in your database. To do this, edit the C file and update it to match: + # rename this file to MyApp.yml and put a : in front of "name" if + # you want to use yaml like in old versions of Catalyst name MyApp default_realm dbic - # Note this first definition would be the same as setting + # Note: this first definition would be the same as setting # __PACKAGE__->config->{authentication}->{realms}->{dbic} - # ->{credential} = 'Password' in lib/MyApp.pm + # ->{credential} = 'Password' in lib/MyApp.pm # # Specify that we are going to do password-based auth class Password # This is the name of the field in the users table with the # password stored in it password_field password - # We are using an unencrypted password now + # We are using an unencrypted password for now password_type clear # Use DBIC to retrieve username, password & role information class DBIx::Class - # This is the model object created by Catalyst::Model::DBIC - # from your schema (you created 'MyApp::Schema::User' but as - # the Catalyst startup debug messages show, it was loaded as - # 'MyApp::Model::DB::Users'). - # NOTE: Omit 'MyApp::Model' here just as you would when using + # This is the model object created by Catalyst::Model::DBIC + # from your schema (you created 'MyApp::Schema::Result::User' + # but as the Catalyst startup debug messages show, it was + # loaded as 'MyApp::Model::DB::Users'). + # NOTE: Omit 'MyApp::Model' here just as you would when using # '$c->model("DB::Users)' user_class DB::Users - # This is the name of the field in your 'users' table that - # contains the user's name - id_field username @@ -341,9 +358,6 @@ C file and update it to match: Inline comments in the code above explain how each field is being used. -Note that you can use many other config file formats with catalyst. -See L -for details. =head2 Add Login and Logout Controllers @@ -352,97 +366,94 @@ Use the Catalyst create script to create two stub controller files: $ script/myapp_create.pl controller Login $ script/myapp_create.pl controller Logout -B You could easily use a single controller here. For example, -you could have a C controller with both C and C -actions. Remember, Catalyst is designed to be very flexible, and leaves -such matters up to you, the designer and programmer. +You could easily use a single controller here. For example, you could +have a C controller with both C and C actions. +Remember, Catalyst is designed to be very flexible, and leaves such +matters up to you, the designer and programmer. -Then open C, locate the C method (or C if you are using an -older version of Catalyst) that was automatically inserted by the -helpers when we created the Login controller above, and delete this -line: - - $c->response->body('Matched MyApp::Controller::Login in Login.'); - -Then update it to match: +Then open C, locate the +C method (or C if you +are using an older version of Catalyst) that was automatically +inserted by the helpers when we created the Login controller above, +and update the definition of C to match: =head2 index - + Login logic - + =cut - + sub index :Path :Args(0) { my ($self, $c) = @_; - + # Get the username and password from form my $username = $c->request->params->{username} || ""; my $password = $c->request->params->{password} || ""; - + # If the username and password values were found in form if ($username && $password) { # Attempt to log the user in - if ($c->authenticate({ username => $username, - password => $password} )) { + if ($c->authenticate({ username => $username, + password => $password } )) { # If successful, then let them use the application - $c->response->redirect($c->uri_for('/books/list')); + $c->response->redirect($c->uri_for( + $c->controller('Books')->action_for('list'))); return; } else { # Set an error message $c->stash->{error_msg} = "Bad username or password."; } } - + # If either of above don't work out, send to the login page $c->stash->{template} = 'login.tt2'; } This controller fetches the C and C values from the -login form and attempts to authenticate the user. If successful, it -redirects the user to the book list page. If the login fails, the user -will stay at the login page but receive an error message. If the -C and C values are not present in the form, the +login form and attempts to authenticate the user. If successful, it +redirects the user to the book list page. If the login fails, the user +will stay at the login page and receive an error message. If the +C and C values are not present in the form, the user will be taken to the empty login form. -Note that we could have used something like C (or -even C; however, the use of C actions -is discouraged because it does not receive path args as with other -actions. The recommended practice is to only use C in -C. - -Instead, we are using C here to -specifically match the URL C. C actions (aka, "literal -actions") create URI matches relative to the namespace of the -controller where they are defined. Although C supports -arguments that allow relative and absolute paths to be defined, here -we use an empty C definition to match on just the name of the -controller itself. The method name, C, is arbitrary. We make +Note that we could have used something like "C", +however, it is generally recommended (partly for historical reasons, +and partly for code clarity) only to use C in +C, and then mainly to generate the 404 not +found page for the application. + +Instead, we are using "C" here to +specifically match the URL C. C actions (aka, "literal +actions") create URI matches relative to the namespace of the +controller where they are defined. Although C supports +arguments that allow relative and absolute paths to be defined, here +we use an empty C definition to match on just the name of the +controller itself. The method name, C, is arbitrary. We make the match even more specific with the C<:Args(0)> action modifier -- -this forces the match on I C, not +this forces the match on I C, not C. -Next, update the corresponding method in +Next, update the corresponding method in C to match: =head2 index - + Logout logic - + =cut - + sub index :Path :Args(0) { my ($self, $c) = @_; - + # Clear the user's state $c->logout; - + # Send the user to the starting point $c->response->redirect($c->uri_for('/')); } -As with the login controller, be sure to delete the -C<$c->response->body('Matched MyApp::Controller::Logout in Logout.');> +As with the login controller, be sure to delete the +C<$c-Eresponse-Ebody('Matched MyApp::Controller::Logout in Logout.');> line of the C. @@ -451,9 +462,9 @@ line of the C. Create a login form by opening C and inserting: [% META title = 'Login' %] - + -
+ @@ -483,27 +494,27 @@ Edit the existing C class file and insert the following method: =head2 auto - + Check if there is a user and, if not, forward to login page - + =cut - + # Note that 'auto' runs after 'begin' but before your actions and that - # 'auto' "chain" (all from application path to most specific class are run) + # 'auto's "chain" (all from application path to most specific class are run) # See the 'Actions' section of 'Catalyst::Manual::Intro' for more info. sub auto : Private { my ($self, $c) = @_; - + # Allow unauthenticated users to reach the login page. This - # allows anauthenticated users to reach any action in the Login + # allows unauthenticated users to reach any action in the Login # controller. To lock it down to a single action, we could use: # if ($c->action eq $c->controller('Login')->action_for('index')) - # to only allow unauthenticated access to the C action we + # to only allow unauthenticated access to the 'index' action we # added above. if ($c->controller eq $c->controller('Login')) { return 1; } - + # If a user doesn't exist, force login if (!$c->user_exists) { # Dump a log message to the development server debug output @@ -513,68 +524,19 @@ the following method: # Return 0 to cancel 'post-auto' processing and prevent use of application return 0; } - + # User found, so return 1 to continue with processing after this 'auto' return 1; } - -B Catalyst provides a number of different types of actions, -such as C, C, C and the new C. You -should refer to L for -a more detailed explanation, but the following bullet points provide a -quick introduction: - -=over 4 - -=item * - -The majority of application have traditionally use C actions -for items that respond to user requests and C actions for -those that do not directly respond to user input. - -=item * - -Newer Catalyst applications tend to use C actions and the -C attribute because of their power and flexibility. You can -specify the path to match relative to the namespace of the current -module as an argument to C. For example C in -C would match on the URL -C but C would -match on C. - -=item * - -Automatic "chaining" of actions by the dispatcher is a powerful -feature that allows multiple methods to handle a single URL. See -L -for more information on chained actions. - -=item * - -There are five types of build-in C actions: C, C, -C, C, and C. - -=item * - -With C, C, C, C private actions, only the -most specific action of each type will be called. For example, if you -define a C action in your controller it will I a -C action in your application/root controller -- I the -action in your controller will be called. - -=item * - -Unlike the other actions where only a single method is called for each -request, I auto action along the chain of namespaces will be -called. Each C action will be called I. - -=back - -By placing the authentication enforcement code inside the C method -of C (or C), it will be -called for I request that is received by the entire application. +As discussed in +L, +every C method from the application/root controller down to the +most specific controller will be called. By placing the +authentication enforcement code inside the C method of +C (or C), it will be +called for I request that is received by the entire +application. =head2 Displaying Content Only to Authenticated Users @@ -584,22 +546,23 @@ changes depending on whether the user has authenticated yet. To do this, open C in your editor and add the following lines to the bottom of the file: + ...

[% - # This code illustrates how certain parts of the TT + # This code illustrates how certain parts of the TT # template will only be shown to users who have logged in %] - [% IF Catalyst.user_exists %] - Please Note: You are already logged in as '[% Catalyst.user.username %]'. - You can logout here. + [% IF c.user_exists %] + Please Note: You are already logged in as '[% c.user.username %]'. + You can logout here. [% ELSE %] You need to log in to use this application. [% END %] [%# Note that this whole block is a comment because the "#" appears - immediate after the "[%" (with no spaces in between). Although it - can be a handy way to temporarily "comment out" a whole block of - TT code, it's probably a little too subtle for use in "normal" + immediate after the "[%" (with no spaces in between). Although it + can be a handy way to temporarily "comment out" a whole block of + TT code, it's probably a little too subtle for use in "normal" comments. %]

@@ -618,34 +581,47 @@ running) and restart it: $ script/myapp_server.pl -B If you are having issues with authentication on -Internet Explorer, be sure to check the system clocks on both your -server and client machines. Internet Explorer is very picky about -timestamps for cookies. Note that you can quickly sync an Ubuntu -system with the following command: +B If you are having issues with authentication on +Internet Explorer, be sure to check the system clocks on both your +server and client machines. Internet Explorer is very picky about +timestamps for cookies. You can quickly sync a Debian system by +installing the "ntpdate" package: + + sudo aptitude -y install ntpdate + +And then run the following command: - sudo ntpdate ntp.ubuntu.com + sudo ntpdate-debian + +Or, depending on your firewall configuration: + + sudo ntpdate-debian -u + +Note: NTP can be a little more finicky about firewalls because it uses +UDP vs. the more common TCP that you see with most Internet protocols. +Worse case, you might have to manually set the time on your development +box instead of using NTP. Now trying going to L and you should -be redirected to the login page, hitting Shift+Reload if necessary (the -"You are already logged in" message should I appear -- if it does, -click the C button and try again). Note the C<***Root::auto User -not found...> debug message in the development server output. Enter -username C and password C, and you should be taken to -the Book List page. +be redirected to the login page, hitting Shift+Reload or Ctrl+Reload +if necessary (the "You are already logged in" message should I +appear -- if it does, click the C button and try again). Note +the C<***Root::auto User not found...> debug message in the +development server output. Enter username C and password +C, and you should be taken to the Book List page. Open C and add the following lines to the bottom (below the closing
Username:
tag):

- Login - Create + Login + Create

-Reload your browser and you should now see a "Login" and "Create" links -at the bottom of the page (as mentioned earlier, you can update template -files without reloading the development server). Click the first link -to return to the login page. This time you I see the "You are +Reload your browser and you should now see a "Login" and "Create" links +at the bottom of the page (as mentioned earlier, you can update template +files without reloading the development server). Click the first link +to return to the login page. This time you I see the "You are already logged in" message. Finally, click the C link on the C page. @@ -661,12 +637,16 @@ from cleartext passwords to SHA-1 password hashes. B This section is optional. You can skip it and the rest of the tutorial will function normally. -Note that even with the techniques shown in this section, the browser +Be aware that even with the techniques shown in this section, the browser still transmits the passwords in cleartext to your application. We are just avoiding the I of cleartext passwords in the database by using a SHA-1 hash. If you are concerned about cleartext passwords between the browser and your application, consider using SSL/TLS, made -easy with the Catalyst plugin Catalyst::Plugin:RequireSSL. +easy with the Catalyst plugin Catalyst::Plugin:RequireSSL. You should +also consider adding a "salt" mechanism to your hashed passwords to +mitigate the risk of a "rainbow table" crack against your passwords (see +L +for more information on using a salt value). =head2 Get a SHA-1 Hash for the Password @@ -679,12 +659,6 @@ dirty" way to do this: $ perl -MDigest::SHA -e 'print Digest::SHA::sha1_hex("mypass"), "\n"' e727d1464ae12436e899a726da5b2f11d8381b26 - $ - -B If you are following along in Ubuntu, you will need to install -C with the following command to run the example code above: - - sudo apt-get install libdigest-sha-perl B You should probably modify this code for production use to not read the password from the command line. By having the script @@ -715,12 +689,13 @@ B We are using SHA-1 hashes here, but many other hashing algorithms are supported. See C for more information. -=head2 Enable SHA-1 Hash Passwords in -C +=head2 Enable SHA-1 Hash Passwords in Catalyst::Plugin::Authentication::Store::DBIC Edit C and update it to match (the C and C are new, everything else is the same): + # rename this file to MyApp.yml and put a : in front of "name" if + # you want to use yaml like in old versions of Catalyst name MyApp default_realm dbic @@ -729,7 +704,7 @@ C are new, everything else is the same): # Note this first definition would be the same as setting # __PACKAGE__->config->{authentication}->{realms}->{dbic} - # ->{credential} = 'Password' in lib/MyApp.pm + # ->{credential} = 'Password' in lib/MyApp.pm # # Specify that we are going to do password-based auth class Password @@ -744,21 +719,19 @@ C are new, everything else is the same): # Use DBIC to retrieve username, password & role information class DBIx::Class - # This is the model object created by Catalyst::Model::DBIC - # from your schema (you created 'MyApp::Schema::User' but as - # the Catalyst startup debug messages show, it was loaded as - # 'MyApp::Model::DB::Users'). - # NOTE: Omit 'MyApp::Model' here just as you would when using + # This is the model object created by Catalyst::Model::DBIC + # from your schema (you created 'MyApp::Schema::Result::User' + # but as the Catalyst startup debug messages show, it was + # loaded as 'MyApp::Model::DB::Users'). + # NOTE: Omit 'MyApp::Model' here just as you would when using # '$c->model("DB::Users)' user_class DB::Users - # This is the name of the field in your 'users' table that - # contains the user's name - id_field username + =head2 Try Out the Hashed Passwords Press C to kill the previous server instance (if it's still @@ -767,66 +740,71 @@ running) and restart it: $ script/myapp_server.pl You should now be able to go to L and -login as before. When done, click the "Logout" link on the login page +login as before. When done, click the "logout" link on the login page (or point your browser at L). =head1 USING THE SESSION FOR FLASH -As discussed in Part 3 of the tutorial, C allows you to set -variables in a way that is very similar to C, but it will -remain set across multiple requests. Once the value is read, it -is cleared (unless reset). Although C has nothing to do with -authentication, it does leverage the same session plugins. Now that -those plugins are enabled, let's go back and improve the "delete -and redirect with query parameters" code seen at the end of the -L part of the -tutorial. +As discussed in the previous chapter of the tutorial, C allows +you to set variables in a way that is very similar to C, but it +will remain set across multiple requests. Once the value is read, it +is cleared (unless reset). Although C has nothing to do with +authentication, it does leverage the same session plugins. Now that +those plugins are enabled, let's go back and update the "delete and +redirect with query parameters" code seen at the end of the L chapter of the tutorial to +take advantage of C. First, open C and modify C to match the following (everything after the model search line of code has changed): - =head2 delete - + =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('DB::Books')->search({id => $id})->delete_all; - + + sub delete :Chained('object') :PathPart('delete') :Args(0) { + my ($self, $c) = @_; + + # Use the book object saved by 'object' and delete it along + # with related 'book_authors' entries + $c->stash->{object}->delete; + # Use 'flash' to save information across requests until it's read $c->flash->{status_msg} = "Book deleted"; - + # Redirect the user back to the list page - $c->response->redirect($c->uri_for('/books/list')); + $c->response->redirect($c->uri_for($self->action_for('list'))); } -Next, open C and update the TT code to pull from +Next, open C and update the TT code to pull from flash vs. the C query parameter: - - + ...
- [% status_msg || Catalyst.flash.status_msg %] - [% error_msg %] - [% content %] -
- - + [%# Status and error messages %] + [% status_msg || c.flash.status_msg %] + [% error_msg %] + [%# This is where TT will stick all of your template's contents. -%] + [% content %] + + ... + +Although the sample above only shows the C div, leave the +rest of the file intact -- the only change we made to the C +was to add "C<|| c.request.params.status_msg>" to the +Cspan class="message"E> line. =head2 Try Out Flash -Restart the development server and point your browser to -L to create an extra -several books. Click the "Return to list" link and delete one of the -"Test" books you just added. The C mechanism should retain our +Restart the development server, log in, and then point your browser to +L to create an extra +several books. Click the "Return to list" link and delete one of the +"Test" books you just added. The C mechanism should retain our "Book deleted" status message across the redirect. B While C will save information across multiple requests, @@ -837,16 +815,16 @@ after that first time (unless you reset it). Please refer to L for additional information. + =head2 Switch To Flash-To-Stash -Although the a use of flash above is certainly an improvement over the -C we employed in Part 4 of the tutorial, the C statement is a little ugly. A nice -alternative is to use the C feature that automatically -copies the content of flash to stash. This makes your code controller -and template code work regardless of where it was directly access, a -forward, or a redirect. To enable C, you can either -set the value in C by changing the default +Although the a use of flash above works well, the +C statement is a little ugly. A nice +alternative is to use the C feature that automatically +copies the content of flash to stash. This makes your controller +and template code work regardless of where it was directly access, a +forward, or a redirect. To enable C, you can either +set the value in C by changing the default C<__PACKAGE__-Econfig> setting to something like: __PACKAGE__->config( @@ -860,20 +838,20 @@ B add the following to C: flash_to_stash 1 -The C<__PACKAGE__-Econfig> option is probably preferable here -since it's not something you will want to change at runtime without it +The C<__PACKAGE__-Econfig> option is probably preferable here +since it's not something you will want to change at runtime without it possibly breaking some of your code. -Then edit C and change the C line -to look like the following: +Then edit C and change the C line +to match the following: [% status_msg %] Restart the development server and go to -L in your browser. Delete another +L in your browser. Delete another of the "Test" books you added in the previous step. Flash should still maintain the status message across the redirect even though you are no -longer explicitly accessing C. +longer explicitly accessing C. =head1 AUTHOR @@ -882,7 +860,7 @@ Kennedy Clark, C Please report any errors, issues or suggestions to the author. The most recent version of the Catalyst Tutorial can be found at -L. +L. -Copyright 2006, Kennedy Clark, under Creative Commons License -(L). +Copyright 2006-2008, Kennedy Clark, under Creative Commons License +(L).