Revision history for Catalyst-Manual
+5.8001 15 Nov 2009
+ - Update tutorial to match latest prepacked versions in Debian 5
+ - Add FormHandler branch (with thanks to gshank!)
+ - Misc cleanup/freshing up of tutorial.
+ - Fix indenting issue (with thanks to Kiffin Gish)
+ - Integrate tome fix branch (with thanks to tome!)
+ - Add a "negative" test to confirm that test02 does not have an admin create link
+ - Integrate sqlite3 clarification and link by wolfman2000 from tutorial_role_updates branch
+ - Fix Pod typos in ::Internals (RT#51488)
+ - Fix Pod typos in the Cookbook (RT#51466)
+ - Fix a Test::Pod failure and make Debian happier.
+ - Typo fixes from garu
+ - Misc minor and/or typo fixes
+
5.8001 06 Oct 2009
- Tutorial
- Fix RT #46760
use strict;
use warnings;
-our $VERSION = '5.8001';
+our $VERSION = '5.8002';
=head1 NAME
=over
-=item B</cd/<ID>/track/<ID>>
+=item B<< /cd/<ID>/track/<ID> >>
Displays info on a particular track.
In the case of a multi-volume CD, this is the track sequence.
-=item B</cd/<ID>/volume/<ID>/track/<ID>>
+=item B<< /cd/<ID>/volume/<ID>/track/<ID> >>
Displays info on a track on a specific volume.
=item 1
When the Catalyst module is imported in the main application
-module it stores any options.
+module, it stores any options.
=item 2
-WHen C<< __PACKAGE__->setup >> is called, it evaluates any
-options stored (C<-Debug>, C<-Engine=XXX>), makes the application
+When C<< __PACKAGE__->setup >> is called, it evaluates any
+options stored (C<-Debug>, C<-Engine=XXX>), and makes the application
inherit from L<Catalyst> (if that hasn't already been done with an
explicit C<< use base 'Catalyst'; >> or C<< extends 'Catalyst'; >>.
Any specified plugins are then loaded, the application module is made to
Matches any URL beginning with> http://localhost:3000/my/controller/foo. The namespace and
subroutine name together determine the path.
-=item * Namespace-level (C<:Global>)
+=item * Root-level (C<:Global>)
package MyApp::Controller::Foo;
sub foo : Global { }
Matches http://localhost:3000/foo - that is, the action is mapped
directly to the controller namespace, ignoring the function name.
-C<:Global> is equivalent C<:Local> one level higher in
-the namespace.
+C<:Global> always matches from root: it is sugar for C<:Path('/methodname')>.
+C<:Local> is simply sugar for C<:Path('methodname')>, which takes the package
+namespace as described above.
package MyApp::Controller::Root;
__PACKAGE__->config->{namespace}='';
match precisely.
No :Args at all means that B<any number> of arguments are taken. Thus, any
-URL that B<starts with> the controller's path will match.
+URL that B<starts with> the controller's path will match. Obviously, this means
+you cannot chain from an action that does not specify args, as the next action
+in the chain will be swallowed as an arg to the first!
=item * Literal match (C<:Path>)
If you don't want or need these features then it's perfectly acceptable
(and faster) to do something like this:
-sub hello : Global {
- my ( $self, $c ) = @_;
- $c->stash->{message} = 'Hello World!';
- $self->check_message( $c, 'test1' );
-}
-
-sub check_message {
- my ( $self, $c, $first_argument ) = @_;
- # do something...
-}
+ sub hello : Global {
+ my ( $self, $c ) = @_;
+ $c->stash->{message} = 'Hello World!';
+ $self->check_message( $c, 'test1' );
+ }
+
+ sub check_message {
+ my ( $self, $c, $first_argument ) = @_;
+ # do something...
+ }
Note that C<forward> returns to the calling action and continues
processing after the action finishes. If you want all further processing
=item *
-The use of L<HTML::FormFu|HTML::FormFu> for automated form processing
-and validation.
+The use of L<HTML::FormFu|HTML::FormFu> or L<HTML::FormHandler|HTML::FormHandler>
+for automated form processing and validation.
=back
=item *
-Catalyst::Devel v1.15
+Catalyst::Devel v1.21
=item *
-DBIx::Class v0.08108
+DBIx::Class v0.08112
=item *
=item *
-Catalyst::Plugin::Authentication -- v0.10011
+Catalyst::Plugin::Authentication -- v0.10015
=item *
=item *
-Catalyst::Plugin::ConfigLoader -- v0.23
+Catalyst::Plugin::ConfigLoader -- v0.27
=item *
-Catalyst::Plugin::Session -- v0.22
+Catalyst::Plugin::Session -- v0.29
=item *
-Catalyst::Plugin::Session::State::Cookie -- v0.11
+Catalyst::Plugin::Session::State::Cookie -- v0.17
=item *
-Catalyst::Plugin::Session::Store::FastMmap -- v0.10
+Catalyst::Plugin::Session::Store::FastMmap -- v0.13
=item *
-Catalyst::Plugin::StackTrace -- v0.10
+Catalyst::Plugin::StackTrace -- v0.11
=item *
-Catalyst::Plugin::Static::Simple -- v0.21
+Catalyst::Plugin::Static::Simple -- v0.25
=back
sudo aptitude -y install sqlite3 libdbd-sqlite3-perl libcatalyst-perl \
libcatalyst-modules-perl libdbix-class-timestamp-perl \
libdbix-class-encodedcolumn-perl libperl6-junction-perl \
- libdatetime-format-sqlite-perl
+ libdatetime-format-sqlite-perl libconfig-general-perl \
+ libhtml-formfu-model-dbic-perl
Let it install (normally about a 30 to 90-second operaton) and you are
done. (Note the '\' above. Depending on your environment, you might
[debug] Statistics enabled
[debug] Loaded plugins:
.----------------------------------------------------------------------------.
- | Catalyst::Plugin::ConfigLoader 0.23 |
- | Catalyst::Plugin::Static::Simple 0.21 |
+ | Catalyst::Plugin::ConfigLoader 0.27 |
+ | Catalyst::Plugin::Static::Simple 0.25 |
'----------------------------------------------------------------------------'
[debug] Loaded dispatcher "Catalyst::Dispatcher"
=head1 CREATE A SQLITE DATABASE
In this step, we make a text file with the required SQL commands to
+create a database table and load some sample data. We will use
+SQLite (L<http://www.sqlite.org>), a popular database that is
+lightweight and easy to use. Be sure to get at least version 3. Open
create a database table and load some sample data. We will use
L<SQLite|http://www.sqlite.org>, a popular database that is
lightweight and easy to use. Be sure to get at least version 3. Open
your OS command prompt.
Please note that here we have chosen to use 'singular' table names. This
-is because the default inflection code for L<DBIx::Class:Schema::Loader>
+is because the default inflection code for L<DBIx::Class::Schema::Loader>
does NOT handle plurals. There has been much philosophical discussion
on whether table names should be plural or singular. There is no one
correct answer, as long as one makes a choice and remains consistent
with it. If you prefer plural table names (e.g. they are easier and
more natural to read) then you will need to pass it an inflect_map
-option. See L<DBIx::Class:Schema::Loader> for more information.
+option. See L<DBIx::Class::Schema::Loader> for more information.
For using other databases, such as PostgreSQL or MySQL, see
L<Appendix 2|Catalyst::Manual::Tutorial::10_Appendices>.
$ perl -MCatalyst::Model::DBIC::Schema -e \
'print "$Catalyst::Model::DBIC::Schema::VERSION\n"'
- 0.23
+ 0.31
Please note the '\' above. Depending on your environment, you might
be able to cut and paste the text as shown or need to remove the '\'
character to that the command is all on a single line.
-You should have version 0.23 or greater if you are following along
+You should have version 0.31 or greater if you are following along
with Debian 5. In other environments, you may need to run this
command to install it directly from CPAN:
$ sudo cpan Catalyst::Model::DBIC::Schema
And re-run the version print command to verify that you are now at
-0.23 or higher.
+0.31 or higher.
=head2 Create Static DBIx::Class Schema Files
=item *
-C<components=TimeStamp> causes the help to include the
-L<DBIx::Class::TimeStamp|DBIx::Class::TimeStamp> DBIC component.
-
-=item *
-
And finally, C<dbi:SQLite:myapp.db> is the standard DBI connect string
for use with SQLite.
[debug] Statistics enabled
[debug] Loaded plugins:
.----------------------------------------------------------------------------.
- | Catalyst::Plugin::ConfigLoader 0.22 |
- | Catalyst::Plugin::StackTrace 0.09 |
- | Catalyst::Plugin::Static::Simple 0.21 |
+ | Catalyst::Plugin::ConfigLoader 0.27 |
+ | Catalyst::Plugin::StackTrace 0.11 |
+ | Catalyst::Plugin::Static::Simple 0.25 |
'----------------------------------------------------------------------------'
[debug] Loaded dispatcher "Catalyst::Dispatcher"
| /books/list | /books/list |
'-------------------------------------+--------------------------------------'
- [info] MyApp powered by Catalyst 5.80003
+ [info] MyApp powered by Catalyst 5.80013
You can connect to your server at http://debian:3000
B<NOTE:> Be sure you run the C<script/myapp_server.pl> command from
see the six DBIC debug messages similar to the following (where
N=1-6):
- SELECT author.id, author.first_name, author.last_name \
- FROM book_author me JOIN author author \
+ SELECT author.id, author.first_name, author.last_name
+ FROM book_author me JOIN author author
ON author.id = me.author_id WHERE ( me.book_id = ? ): 'N'
-(The '\' characters won't actually appear in the output -- we are
-using them as "line continuation markers" here.)
-
=head1 CONVERT TO A CHAINED ACTION
to the following code:
<Plugin::Authentication>
- use_session 1
<default>
password_type clear
user_model DB::User
Session::State::Cookie
/;
-Once again (remain sharp, by now you should be getting the hang of things)
-include this additional plugin as a new dependency in the Makefile.PL file
-like this:
+Once again, include this additional plugin as a new dependency in
+the Makefile.PL file like this:
requires (
...
# Make sure the appropriate logout buttons are displayed
$_->content_contains("/logout\">User Logout</a>",
"Both users should have a 'User Logout'") for $ua1, $ua2;
- $ua1->content_contains("/books/form_create\">Create</a>",
- "Only 'test01' should have a create link");
+ $ua1->content_contains("/books/form_create\">Admin Create</a>",
+ "'test01' should have a create link");
+ $ua2->content_lacks("/books/form_create\">Admin Create</a>",
+ "'test02' should NOT have a create link");
$ua1->get_ok("http://localhost/books/list", "View book list as 'test01'");
=item *
+L<FormHandler|Catalyst::Manual::Tutorial::09_AdvancedCRUD::09_FormHandler>
+
+=item *
+
L<FormBuilder|Catalyst::Manual::Tutorial::09_AdvancedCRUD::09_FormBuilder>
=back
This portion of the tutorial explores L<HTML::FormFu|HTML::FormFu> and
how it can be used to manage forms, perform validation of form input,
as well as save and restore data to/from the database. This was written
-using HTML::FormFu version 0.03007.
+using HTML::FormFu version 0.05001.
See
L<Catalyst::Manual::Tutorial::09_AdvancedCRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD>
L<HTML::FormFu|HTML::FormFu>.
-=head1 Install HTML::FormFu
-
-If you are following along in Debian 5, it turns out that some of the
-modules we need are not yet available as Debian packages at the time
-this was written. To install it with a combination of Debian packages
-and traditional CPAN modules, first use C<aptitude> to install most of
-the modules:
-
-we need to install the
-L<HTML::FormFu|HTML::FormFu> package:
-
- sudo aptitude -y install libhtml-formfu-perl libmoose-perl \
- libregexp-assemble-perl libhtml-formfu-model-dbic-perl
-
- ...
-
- sudo aptitude clean
-
-Then use the following command to install directly from CPAN the modules
-that aren't available as Debian packages:
-
- sudo cpan Catalyst::Component::InstancePerContext Catalyst::Controller::HTML::FormFu
-
-
=head1 HTML::FormFu FORM CREATION
This section looks at how L<HTML::FormFu|HTML::FormFu> can be used to
click the new HTML::FormFu "Create" link at the bottom to display the
form. Fill in the following values:
- Title = "Internetworking with TCP/IP Vol. II"
- Rating = "4"
- Author = "Comer"
+Title: Internetworking with TCP/IP Vol. II
+Rating: 4
+Author: Comer
Click the Submit button, and you will be returned to the Book List page
with a "Book created" status message displayed.
We make sure the book lookup returned a valid book. If not, we set
the error message and return to the book list.
-
+
=item *
If the form has been submitted and passes validation, we skip creating a
--- /dev/null
+=head1 NAME
+
+Catalyst::Manual::Tutorial::09_AdvancedCRUD::09_FormHandler - Catalyst Tutorial - Chapter 9: Advanced CRUD - FormHandler
+
+
+=head1 OVERVIEW
+
+This is B<Chapter 9 of 10> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::01_Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics>
+
+=item 3
+
+L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
+
+=item 4
+
+L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
+
+=item 5
+
+L<Authentication|Catalyst::Manual::Tutorial::05_Authentication>
+
+=item 6
+
+L<Authorization|Catalyst::Manual::Tutorial::06_Authorization>
+
+=item 7
+
+L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
+
+=item 8
+
+L<Testing|Catalyst::Manual::Tutorial::08_Testing>
+
+=item 9
+
+B<09_Advanced CRUD::09_FormHandler>
+
+=item 10
+
+L<Appendices|Catalyst::Manual::Tutorial::10_Appendices>
+
+=back
+
+
+=head1 DESCRIPTION
+
+This portion of the tutorial explores L<HTML::FormHandler|HTML::FormHandler> and
+how it can be used to manage forms, perform validation of form input,
+as well as save and restore data to/from the database. This was written
+using HTML::FormHandler version 0.28001.
+
+See
+L<Catalyst::Manual::Tutorial::09_AdvancedCRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD>
+for additional form management options other than
+L<HTML::FormHandler|HTML::FormHandler>.
+
+
+=head1 Install HTML::FormHandler
+
+
+Use the following command to install L<HTML::FormHandler::Model::DBIC> directly
+from CPAN:
+
+ sudo cpan HTML::FormHandler::Model::DBIC
+
+It will install L<HTML::FormHandler> as a prereq.
+
+
+=head1 HTML::FormHandler FORM CREATION
+
+This section looks at how L<HTML::FormHandler|HTML::FormHandler> can be used to
+add additional functionality to the manually created form from Chapter 4.
+
+
+=head2 Using FormHandler in your controllers
+
+FormHandler doen't have a Catalyst base controller, because interfacing
+to a form is only a couple of lines of code.
+
+=head2 Create a Book Form
+
+Create the directory C<lib/MyApp/Form>. Create C<lib/MyApp/Form/Book.pm>:
+
+ package MyApp::Form::Book;
+ use HTML::FormHandler::Moose;
+ extends 'HTML::FormHandler::Model::DBIC';
+ use namespace::autoclean;
+
+ has '+item_class' => ( default =>'Books' );
+ has_field 'title';
+ has_field 'rating' => ( type => 'Integer' );
+ has_field 'authors' => ( type => 'Multiple', label_column => 'last_name' );
+ has_field 'submit' => ( type => 'Submit', value => 'Submit' );
+
+ __PACKAGE__->meta->make_immutable;
+ 1;
+
+
+=head2 Add Action to Display and Save the Form
+
+At the top of the C<lib/MyApp/Controller/Books.pm> add:
+
+ use MyApp::Form::Book;
+
+Add the following methods:
+
+ =head2 create
+
+ Use HTML::FormHandler to create a new book
+
+ =cut
+
+ sub create : Chained('base') PathPart('create') Args(0) {
+ my ($self, $c ) = @_;
+
+ my $book = $c->model('DB::Book')->new_result({});
+ return $self->form($c, $book);
+ }
+
+ =head2 form
+
+ Process the FormHandler book form
+
+ =cut
+
+ sub form {
+ my ( $self, $c, $book ) = @_;
+
+ my $form = MyApp::Form::Book->new;
+ # Set the template
+ $c->stash( template => 'books/form.tt2', form => $form );
+ $form->process( item => $book, params => $c->req->params );
+ return unless $form->validated;
+ $c->flash( message => 'Book created' );
+ # Redirect the user back to the list page
+ $c->response->redirect($c->uri_for($self->action_for('list')));
+ }
+
+These two methods could be combined at this point, but we'll use the 'form'
+method later when we implement 'edit'.
+
+
+=head2 Create a Template Page To Display The Form
+
+Open C<root/src/books/form.tt2> in your editor and enter the following:
+
+ [% META title = 'Create/Update Book' %]
+
+ [%# Render the HTML::FormHandler Form %]
+ [% form.render %]
+
+ <p><a href="[% c.uri_for(c.controller.action_for('list')) %]">Return to book list</a></p>
+
+
+=head2 Add Link for Create
+
+Open C<root/src/books/list.tt2> in your editor and add the following to
+the bottom of the existing file:
+
+ ...
+ <p>
+ HTML::FormHandler:
+ <a href="[% c.uri_for(c.controller.action_for('create')) %]">Create</a>
+ </p>
+
+This adds a new link to the bottom of the book list page that we can
+use to easily launch our HTML::FormHandler-based form.
+
+
+=head2 Test The HTML::FormHandler Create Form
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still
+running) and restart it:
+
+ $ script/myapp_server.pl
+
+Login as C<test01> (password: mypass). Once at the Book List page,
+click the new HTML::Formhandler "Create" link at the bottom to display the
+form. Fill in the following values:
+
+ Title = "Internetworking with TCP/IP Vol. II"
+ Rating = "4"
+ Author = "Comer"
+
+Click the Submit button, and you will be returned to the Book List page
+with a "Book created" status message displayed.
+
+Note that because the 'Author' column is a Select list, only the authors
+in the database can be entered. The 'ratings' field will only accept
+integers.
+
+
+=head2 Add Constraints
+
+Open C<lib/MyApp/Form/Book.pm> in your editor.
+
+Restrict the title size and make it required:
+
+ has_field 'title' => ( minlength => 5, maxlength => 40, required => 1 );
+
+Add range constraints to the 'rating' field:
+
+ has_field 'rating' => ( type => 'Integer', range_start => 1, range_end => 5 );
+
+The 'authors' relationship is a 'many-to-many' pseudo-relation, so this field
+can be set to Multiple to allow the selection of multiple authors and make it
+required:
+
+ has_field 'authors' => ( type => 'Multiple', required => 1 );
+
+Note: FormHandler automatically strips whitespace at the beginning or end of fields.
+If you want some other kind of stripping (or none) you can specify it explicitly.
+(see L<HTML::FormHandler::Manual>)
+
+=head2 Try Out the Updated Form
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still
+running) and restart it:
+
+ $ script/myapp_server.pl
+
+Make sure you are still logged in as C<test01> and try adding a book
+with various errors: title less than 5 characters, non-numeric rating, a
+rating of 0 or 6, etc. Also try selecting one, two, and zero authors.
+
+=head2 Create the 'edit' method
+
+Edit C<lib/MyApp/Controller/Books.pm> and add the following method:
+
+
+ =head2 edit
+
+ Edit an existing book with FormHandler
+
+ =cut
+
+ sub edit : Chained('object') PathPart('edit') Args(0) {
+ my ( $self, $c ) = @_;
+
+ return $self->form($c, $c->stash->{object});
+ }
+
+Update the C<root/src/books/list.tt2>, adding an 'edit' link below the
+"Delete" link to use the FormHandler edit method:
+
+ <td>
+ [% # Add a link to delete a book %]
+ <a href="[% c.uri_for(c.controller.action_for('delete'), [book.id]) %]">Delete</a>
+ [% # Add a link to edit a book %]
+ <a href="[% c.uri_for(c.controller.action_for('edit'), [book.id]) %]">Edit</a>
+ </td>
+
+
+=head2 Try Out the Edit/Update Feature
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still
+running) and restart it:
+
+ $ script/myapp_server.pl
+
+Make sure you are still logged in as C<test01> and go to the
+L<http://localhost:3000/books/list> URL in your browser. Click the
+"Edit" link next to "Internetworking with TCP/IP Vol. II", change the
+rating to a 3, the "II" at end of the title to the number "2", add
+Stevens as a co-author (control-click), and click Submit. You will then
+be returned to the book list with a "Book edited" message at the top in
+green. Experiment with other edits to various books.
+
+=head2 See additional documentation on FormHandler
+
+L<HTML::FormHandler::Manual>
+
+L<HTML::FormHandler>
+
+ #formhandler on irc.perl.org
+
+ mailing list: http://groups.google.com/group/formhandler
+
+ code: http://github.com/gshank/html-formhandler/tree/master
+
+=head1 AUTHOR
+
+Gerda Shank, C<gshank@cpan.org>
+
+Copyright 2009, Gerda Shank, Perl Artistic License