Add NGINX Unit to the list of PSGI handlers.
-/Catalyst-Manual-*
-/MYMETA.yml
-/MYMETA.json
-/META.yml
-/Makefile
-/blib
-/pm_to_blib
+*#
+*~
+.#*
+/Catalyst-Manual-*.tar
+/Catalyst-Manual-*.tar.gz
+/Catalyst-Manual-*/
+/Distar
/MANIFEST
-/MANIFEST.bak
/MANIFEST.SKIP
+/MANIFEST.bak
+/MYMETA.*
+/Makefile
/Makefile.old
-!/.gitignore
-/Distar*
+/README
+/_eumm/
+/blib/
+/cover_db/
+/pm_to_blib
Revision history for Catalyst-Manual
+5.9010 - 2019-04-25
+ - updated stale urls
+ - numerous typo fixes
+ - many pod syntax fixes
+ - other pod syntax cleanup
- added references to the RT issues queue, mailing list, and irc channel
5.9009 - 2014-12-13
use strict;
use warnings FATAL => 'all';
use ExtUtils::MakeMaker;
-(do 'maint/Makefile.PL.include' or die $@) unless -f 'META.yml';
+(do './maint/Makefile.PL.include' or die $@) unless -f 'META.yml';
my %WriteMakefileArgs = (
NAME => 'Catalyst::Manual',
EVERYTHING
- Paul Makepeace - stuff he threatened to document from mailing list
- Kill all use base
- - Kill all use parent
\ No newline at end of file
+ - Kill all use parent
-# Manual.pm
+# Manual.pm
# Copyright (c) 2006 Jonathan Rockway <jrockway@cpan.org>
package Catalyst::Manual;
use strict;
use warnings;
-our $VERSION = '5.9009';
+our $VERSION = '5.9010';
+
+1;
+__END__
=head1 NAME
=head1 SEE ALSO
-Install L<Task::Catalyst::Tutorial|Task::Catalyst::Tutorial> to
+Install L<Task::Catalyst::Tutorial> to
install all the dependencies you need to follow along with the
-Tutorial. You can also refer to
+Tutorial. You can also refer to
L<Catalyst::Manual::Tutorial::Intro|Catalyst::Manual::Tutorial::01_Intro>
for more information on installation options.
=item *
-L<Catalyst::Manual::About|Catalyst::Manual::About>
+L<Catalyst::Manual::About>
=item *
=item *
-The Definitive Guide to Catalyst: Writing Extendable, Scalable and
+The Definitive Guide to Catalyst: Writing Extendable, Scalable and
Maintainable Perl-Based Web Applications
-By: Kieren Diment, Matt Trout
+By: Kieren Diment, Matt Trout
Available July 12, 2009
ISBN 10: 1-4302-2365-0
ISBN 13: 978-1-4302-2365-8
=head1 SUPPORT
Corrections or amendments may be submitted through L<the RT bug tracker|https://rt.cpan.org/Public/Dist/Display.html?Name=Catalyst-Manual>
-(or L<bug-Catalyst-Manual@rt.cpan.org|mailto:Catalyst-Manual@rt.cpan.org>).
+(or L<bug-Catalyst-Manual@rt.cpan.org|mailto:bug-Catalyst-Manual@rt.cpan.org>).
There is also a mailing list available for users of this distribution, at
L<http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst>.
=head1 AUTHORS
-Catalyst Contributors, see Catalyst.pm
+Catalyst Contributors, see L<Catalyst>
=head1 COPYRIGHT AND LICENCE
the same terms as the Perl 5 programming language system itself.
=cut
-
-1;
L<Maypole>--upon which Catalyst was originally based--designed for the
easy development of powerful web databases; L<Jifty>, which does a great
deal of automation in helping to set up web sites with many complex
-features; and Ruby on Rails (see L<http://www.rubyonrails.org>), written
+features; and Ruby on Rails (see L<https://rubyonrails.org>), written
of course in Ruby and among the most popular web development systems. It
is not the purpose of this document to criticize or even briefly
evaluate these other frameworks; they may be useful for you and if so we
=head1 NAME
-Catalyst::Manual::Actions - Catalyst Reusable Actions
+Catalyst::Manual::Actions - Catalyst Reusable Actions
=head1 DESCRIPTION
This is pretty simple. Actions work just like the normal dispatch
attributes you are used to, like Local or Private:
- sub Hello :Local :ActionClass('SayBefore') {
- $c->res->output( 'Hello '.$c->stash->{what} );
- }
+ sub Hello :Local :ActionClass('SayBefore') {
+ $c->res->output( 'Hello '.$c->stash->{what} );
+ }
In this example, we expect the SayBefore action to magically populate
stash with something relevant before C<Hello> is run. In the next
L<Catalyst::Action> as a base class and decorate the C<execute> call in
the Action class:
- package Catalyst::Action::MyAction;
- use Moose;
- use namespace::autoclean;
-
- extends 'Catalyst::Action';
+ package Catalyst::Action::MyAction;
+ use Moose;
+ use namespace::autoclean;
- before 'execute' => sub {
- my ( $self, $controller, $c, $test ) = @_;
- $c->stash->{what} = 'world';
- };
+ extends 'Catalyst::Action';
- after 'execute' => sub {
- my ( $self, $controller, $c, $test ) = @_;
- $c->stash->{foo} = 'bar';
- };
+ before 'execute' => sub {
+ my ( $self, $controller, $c, $test ) = @_;
+ $c->stash->{what} = 'world';
+ };
- __PACKAGE__->meta->make_immutable;
+ after 'execute' => sub {
+ my ( $self, $controller, $c, $test ) = @_;
+ $c->stash->{foo} = 'bar';
+ };
+
+ __PACKAGE__->meta->make_immutable;
Pretty simple, huh?
The solution to this is to use L<Catalyst::Controller::ActionRole>, which
would make the example above look like this:
- package Catalyst::ActionRole::MyActionRole;
- use Moose::Role;
+ package Catalyst::ActionRole::MyActionRole;
+ use Moose::Role;
+
+ before 'execute' => sub {
+ my ( $self, $controller, $c, $test ) = @_;
+ $c->stash->{what} = 'world';
+ };
- before 'execute' => sub {
- my ( $self, $controller, $c, $test ) = @_;
- $c->stash->{what} = 'world';
- };
+ after 'execute' => sub {
+ my ( $self, $controller, $c, $test ) = @_;
+ $c->stash->{foo} = 'bar';
+ };
- after 'execute' => sub {
- my ( $self, $controller, $c, $test ) = @_;
- $c->stash->{foo} = 'bar';
- };
-
- 1;
+ 1;
and this would be used in a controller like this:
- package MyApp::Controller::Foo;
- use Moose;
- use namespace::autoclean;
- BEGIN { extends 'Catalyst::Controller::ActionRole'; }
+ package MyApp::Controller::Foo;
+ use Moose;
+ use namespace::autoclean;
+ BEGIN { extends 'Catalyst::Controller::ActionRole'; }
- sub foo : Does('MyActionRole') {
- my ($self, $c) = @_;
- }
+ sub foo : Does('MyActionRole') {
+ my ($self, $c) = @_;
+ }
- 1;
+ 1;
=head1 EXAMPLE ACTIONS
-=head2 Catalyst::Action::RenderView
+=head2 L<Catalyst::Action::RenderView>
-This is meant to decorate end actions. It's similar in operation to
+This is meant to decorate end actions. It's similar in operation to
L<Catalyst::Plugin::DefaultEnd>, but allows you to decide on an action
level rather than on an application level where it should be run.
-=head2 Catalyst::Action::REST
+=head2 L<Catalyst::Action::REST>
Provides additional syntax for dispatching based upon the HTTP method
of the request.
=head1 EXAMPLE ACTIONROLES
-=head2 Catalyst::ActionRole::ACL
+=head2 L<Catalyst::ActionRole::ACL>
Provides ACLs for role membership by decorating your actions.
$c->log->info( 'done!' );
}
-You should also be aware that roles in C<< $c-E<gt>setup >> are applied
+You should also be aware that roles in C<< $c->setup >> are applied
after the last plugin with all the benefits of using a single
L<with()|Moose/"with (@roles)"> statement in an ordinary L<Moose> class.
Your class is automatically made immutable at the end of the current file.
-CAVEAT: Using roles in C<< $c-E<gt>setup >> was implemented in Catalyst
+CAVEAT: Using roles in C<< $c->setup >> was implemented in Catalyst
version 5.80004. In prior versions you might get away with
after 'setup_plugins' => sub{ with(
but this is discouraged and you should upgrade to 5.80004 anyway,
because it fixes a few important regressions against 5.71
-CAVEAT: Using roles in C<< $c-E<gt>setup >> will not currently allow
+CAVEAT: Using roles in C<< $c->setup >> will not currently allow
you to pass parameters to roles, or perform conflict resolution.
Conflict detection still works as expected.
=head2 ACCESSORS
-Most of the request-specific attributes like C<$c-E<gt>stash>,
-C<$c-E<gt>request> and C<$c-E<gt>response> have been converted to
+Most of the request-specific attributes like C<< $c->stash >>,
+C<< $c->request >> and C<< $c->response >> have been converted to
L<Moose> attributes but without type constraints, attribute helpers or
builder methods. This ensures that Catalyst 5.8 is fully backwards
compatible to applications using the published API of Catalyst 5.7 but
of L<Moose> attributes.
Most of the accessors to information gathered during compile time (such
-as configuration) are managed by C<Catalyst::ClassData>, which is a
+as configuration) are managed by L<Catalyst::ClassData>, which is a
L<Moose>-aware version of L<Class::Data::Inheritable> but not compatible
with L<MooseX::ClassAttribute>.
use namespace::autoclean;
BEGIN { extends 'Catalyst::Controller'; }
-
+
=head2 Controller Roles
It is possible to use roles to apply method modifiers on controller actions
use Moose;
use namespace::autoclean;
BEGIN { extends 'Catalyst::Controller' };
-
- sub foo : Local {
+
+ sub foo : Local {
my ($self, $c) = @_;
$c->res->body('Hello ');
}
my ($self, $c) = @_;
$c->res->body($c->res->body . 'World');
};
-
+
It is possible to have action methods with attributes inside Moose roles, using
L<MooseX::MethodAttributes>, example:
package MyApp::ControllerRole;
use MooseX::MethodAttributes::Role;
use namespace::autoclean;
-
+
sub foo : Local {
my ($self, $c) = @_;
...
}
-
+
package MyApp::Controller::Foo;
use Moose;
use namespace::autoclean;
BEGIN { extends 'Catalyst::Controller' };
-
+
with 'MyApp::ControllerRole';
=head1 AUTHORS
plugins may be deprecated, or, conversely, may now part of core
L<Catalyst>. Be sure to check the Catalyst:: and CatalystX:: namespaces
for additional components, and consult the mailing list (
-L<http://dev.catalyst.perl.org/wiki/Support> ) for advice on the current
+L<http://wiki.catalystframework.org/wiki/Support> ) for advice on the current
status or preferred use of your chosen plugin/framework.
=head1 PLUGINS
=head4 L<Catalyst::Authentication::Store::DBI>
-Allows you to use a plain L<DBI> database connection to identify users.
+Allows you to use a plain L<DBI> database connection to identify users.
=head4 L<Catalyst::Authentication::Store::Htpasswd>
=head2 L<Catalyst::Plugin::FillInForm>
-A plugin based on C<HTML::FillInForm>, which describes itself as a module
+A plugin based on L<HTML::FillInForm>, which describes itself as a module
to automatically insert data from a previous HTML form into the HTML input,
-textarea, radio buttons, checkboxes, and select tags. C<HTML::FillInForm>
-is a subclass of C<HTML::Parser> and uses it to parse the HTML and insert
+textarea, radio buttons, checkboxes, and select tags. L<HTML::FillInForm>
+is a subclass of L<HTML::Parser> and uses it to parse the HTML and insert
the values into the form tags.
=head2 L<Catalyst::Plugin::Flavour>
=head2 L<Catalyst::Controller::HTML::FormFu>
-Catalyst integration for <HTML::FormFu>.
+Catalyst integration for L<HTML::FormFu>.
=head1 MODELS
=head2 L<Catalyst::Model::CDBI>
-The C<Class::DBI> (CDBI) model class. It is built on top of
-C<Class::DBI::Loader>, which automates the definition of C<Class::DBI>
+The L<Class::DBI> (CDBI) model class. It is built on top of
+L<Class::DBI::Loader>, which automates the definition of L<Class::DBI>
sub-classes by scanning the underlying table schemas, setting up columns
and primary keys.
=head2 L<Catalyst::Model::CDBI::Plain>
-A neutral interface to the C<Class::DBI> module which does not attempt
+A neutral interface to the L<Class::DBI> module which does not attempt
to automate table setup. It allows the user to manually set up
-C<Class::DBI> classes, either by doing so within the Catalyst model
-classes themselves, or by inheriting from existing C<Class::DBI>
+L<Class::DBI> classes, either by doing so within the Catalyst model
+classes themselves, or by inheriting from existing L<Class::DBI>
classes.
=head2 L<Catalyst::Model::DBIC::Schema>
your C<use Catalyst> statement . However, you can also enable it using
environment variable, so you can (for example) get debug info without
modifying your application scripts. Just set C<CATALYST_DEBUG> or
-C<E<lt>MYAPPE<gt>_DEBUG> to a true value.
+C<< <MYAPP>_DEBUG >> to a true value.
=head2 Sessions
=head3 EXAMPLE
- package MyApp;
- use Moose;
- use namespace::autoclean;
-
- use Catalyst qw/
- Session
- Session::Store::FastMmap
- Session::State::Cookie
- /;
- extends 'Catalyst';
- __PACKAGE__->setup;
-
- package MyApp::Controller::Foo;
- use Moose;
- use namespace::autoclean;
- BEGIN { extends 'Catalyst::Controller' };
- ## Write data into the session
-
- sub add_item : Local {
- my ( $self, $c ) = @_;
-
- my $item_id = $c->req->params->{item};
-
- push @{ $c->session->{items} }, $item_id;
+ package MyApp;
+ use Moose;
+ use namespace::autoclean;
+
+ use Catalyst qw/
+ Session
+ Session::Store::FastMmap
+ Session::State::Cookie
+ /;
+ extends 'Catalyst';
+ __PACKAGE__->setup;
+
+ package MyApp::Controller::Foo;
+ use Moose;
+ use namespace::autoclean;
+ BEGIN { extends 'Catalyst::Controller' };
+ ## Write data into the session
+
+ sub add_item : Local {
+ my ( $self, $c ) = @_;
- }
+ my $item_id = $c->req->params->{item};
- ## A page later we retrieve the data from the session:
+ push @{ $c->session->{items} }, $item_id;
+ }
- sub get_items : Local {
- my ( $self, $c ) = @_;
+ ## A page later we retrieve the data from the session:
- $c->stash->{items_to_display} = $c->session->{items};
+ sub get_items : Local {
+ my ( $self, $c ) = @_;
- }
+ $c->stash->{items_to_display} = $c->session->{items};
+ }
=head3 More information
-L<http://search.cpan.org/dist/Catalyst-Plugin-Session>
+L<Catalyst::Plugin::Session>
-L<http://search.cpan.org/dist/Catalyst-Plugin-Session-State-Cookie>
+L<Catalyst::Plugin::Session::State::Cookie>
-L<http://search.cpan.org/dist/Catalyst-Plugin-Session-State-URI>
+L<Catalyst::Plugin::Session::State::URI>
-L<http://search.cpan.org/dist/Catalyst-Plugin-Session-Store-FastMmap>
+L<Catalyst::Plugin::Session::Store::FastMmap>
-L<http://search.cpan.org/dist/Catalyst-Plugin-Session-Store-File>
+L<Catalyst::Plugin::Session::Store::File>
-L<http://search.cpan.org/dist/Catalyst-Plugin-Session-Store-DBI>
+L<Catalyst::Plugin::Session::Store::DBI>
=head2 Configure your application
=head3 Using Config::General
-L<Config::General|Config::General> is a method for creating flexible
+L<Config::General> is a method for creating flexible
and readable configuration files. It's a great way to keep your
Catalyst application configuration in one easy-to-understand location.
-Now create C<myapp.conf> in your application home:
+Now create F<myapp.conf> in your application home:
name MyApp
values, how to access the values in your components, and many 'base'
config variables used internally.
-See also L<Config::General|Config::General>.
+See also L<Config::General>.
=head1 Skipping your VCS's directories
-Catalyst uses Module::Pluggable to load Models, Views, and Controllers.
-Module::Pluggable will scan through all directories and load modules
+Catalyst uses L<Module::Pluggable> to load Models, Views, and Controllers.
+L<Module::Pluggable> will scan through all directories and load modules
it finds. Sometimes you might want to skip some of these directories,
for example when your version control system makes a subdirectory with
meta-information in every version-controlled directory. While
setup_components => { except => qr/SCCS/ },
);
-See the Module::Pluggable manual page for more information on B<except>
+See the L<Module::Pluggable> manual page for more information on B<except>
and other options.
=head1 Users and Access Control
Examples:
- Password - Simple username/password checking.
- HTTPD - Checks using basic HTTP auth.
- TypeKey - Check using the typekey system.
+ Password - Simple username/password checking.
+ HTTPD - Checks using basic HTTP auth.
+ TypeKey - Check using the typekey system.
=head3 Storage backends
Examples:
- DBIC - Storage using a database via DBIx::Class.
- Minimal - Storage using a simple hash (for testing).
+ DBIC - Storage using a database via DBIx::Class.
+ Minimal - Storage using a simple hash (for testing).
=head3 User objects
Examples:
- Hash - A simple hash of keys and values.
+ Hash - A simple hash of keys and values.
=head3 ACL authorization
=head3 EXAMPLE
- package MyApp;
- use Moose;
- use namespace::autoclean;
- extends qw/Catalyst/;
- use Catalyst qw/
- Authentication
- Authorization::Roles
- /;
+ package MyApp;
+ use Moose;
+ use namespace::autoclean;
+ extends qw/Catalyst/;
+ use Catalyst qw/
+ Authentication
+ Authorization::Roles
+ /;
- __PACKAGE__->config(
- authentication => {
- default_realm => 'test',
- realms => {
- test => {
- credential => {
- class => 'Password',
- password_field => 'password',
- password_type => 'self_check',
- },
- store => {
- class => 'Htpasswd',
- file => 'htpasswd',
- },
- },
- },
- },
- );
+ __PACKAGE__->config(
+ authentication => {
+ default_realm => 'test',
+ realms => {
+ test => {
+ credential => {
+ class => 'Password',
+ password_field => 'password',
+ password_type => 'self_check',
+ },
+ store => {
+ class => 'Htpasswd',
+ file => 'htpasswd',
+ },
+ },
+ },
+ },
+ );
- package MyApp::Controller::Root;
- use Moose;
- use namespace::autoclean;
+ package MyApp::Controller::Root;
+ use Moose;
+ use namespace::autoclean;
- BEGIN { extends 'Catalyst::Controller' }
+ BEGIN { extends 'Catalyst::Controller' }
- __PACKAGE__->config(namespace => '');
+ __PACKAGE__->config(namespace => '');
- sub login : Local {
- my ($self, $c) = @_;
+ sub login : Local {
+ my ($self, $c) = @_;
- if ( my $user = $c->req->params->{user}
- and my $password = $c->req->param->{password} )
- {
- if ( $c->authenticate( username => $user, password => $password ) ) {
- $c->res->body( "hello " . $c->user->name );
- } else {
- # login incorrect
- }
- }
- else {
- # invalid form input
- }
- }
+ if ( my $user = $c->req->params->{user}
+ and my $password = $c->req->param->{password} )
+ {
+ if ( $c->authenticate( username => $user, password => $password ) ) {
+ $c->res->body( "hello " . $c->user->name );
+ } else {
+ # login incorrect
+ }
+ }
+ else {
+ # invalid form input
+ }
+ }
- sub restricted : Local {
- my ( $self, $c ) = @_;
+ sub restricted : Local {
+ my ( $self, $c ) = @_;
- $c->detach("unauthorized")
- unless $c->check_user_roles( "admin" );
+ $c->detach("unauthorized")
+ unless $c->check_user_roles( "admin" );
- # do something restricted here
- }
+ # do something restricted here
+ }
=head3 Using authentication in a testing environment
Also, it's important to note that if you restrict access to "/" then
C<end>, C<default>, etc. will also be restricted.
- MyApp->acl_allow_root_internals;
+ MyApp->acl_allow_root_internals;
will create rules that permit access to C<end>, C<begin>, and C<auto> in the
root of your app (but not in any other controller).
Now follow these few steps to implement the application:
-1. Install Catalyst (5.61 or later), Catalyst::Plugin::XMLRPC (0.06 or
-later) and SOAP::Lite (for XMLRPCsh.pl).
+=over 4
+
+=item 1.
-2. Create an application framework:
+Install L<Catalyst> (5.61 or later), L<Catalyst::Plugin::XMLRPC> (0.06 or
+later) and L<SOAP::Lite> (for F<XMLRPCsh.pl>).
+
+=item 2.
+
+Create an application framework:
% catalyst.pl MyApp
...
% cd MyApp
-3. Add the XMLRPC plugin to MyApp.pm
+=item 3.
+
+Add the XMLRPC plugin to MyApp.pm
use Catalyst qw/-Debug Static::Simple XMLRPC/;
-4. Add an API controller
+=item 4.
+
+Add an API controller
% ./script/myapp_create.pl controller API
-5. Add a XMLRPC redispatch method and an add method with Remote
-attribute to lib/MyApp/Controller/API.pm
+=item 5.
+
+Add a XMLRPC redispatch method and an add method with Remote
+attribute to F<lib/MyApp/Controller/API.pm>
sub default :Path {
my ( $self, $c ) = @_;
The C<add> method is not a traditional action; it has no private or
public path. Only the XMLRPC dispatcher knows it exists.
-6. That's it! You have built your first web service. Let's test it with
-XMLRPCsh.pl (part of SOAP::Lite):
+=item 6.
+
+That's it! You have built your first web service. Let's test it with
+F<XMLRPCsh.pl> (part of L<SOAP::Lite>):
% ./script/myapp_server.pl
...
--- XMLRPC RESULT ---
'3'
+=back
+
=head3 Tip
Your return data type is usually auto-detected, but you can easily
Views pertain to the display of your application. As with models,
Catalyst is uncommonly flexible. The recipes below are just a start.
-=head2 Catalyst::View::TT
+=head2 L<Catalyst::View::TT>
One of the first things you probably want to do when starting a new
Catalyst application is set up your View. Catalyst doesn't care how you
L<Template Toolkit|Template> is probably the most popular.
Once again, the Catalyst developers have done all the hard work, and
-made things easy for the rest of us. Catalyst::View::TT provides the
+made things easy for the rest of us. L<Catalyst::View::TT> provides the
interface to Template Toolkit, and provides Helpers which let us set it
up that much more easily.
=head3 Creating your View
-Catalyst::View::TT provides two different helpers for us to use: TT and
+L<Catalyst::View::TT> provides two different helpers for us to use: TT and
TTSite.
=head4 TT
script/myapp_create.pl view TT TT
-This will create lib/MyApp/View/MyView.pm, which is going to be pretty
+This will create F<lib/MyApp/View/MyView.pm>, which is going to be pretty
empty to start. However, it sets everything up that you need to get
started. You can now define which template you want and forward to your
view. For instance:
=over
-=item
+=item *
-INCLUDE_PATH defines the directories that Template Toolkit should search
+C<INCLUDE_PATH> defines the directories that Template Toolkit should search
for the template files.
-=item
+=item *
-PRE_PROCESS is used to process configuration options which are common to
+C<PRE_PROCESS> is used to process configuration options which are common to
every template file.
-=item
+=item *
-WRAPPER is a file which is processed with each template, usually used to
+C<WRAPPER> is a file which is processed with each template, usually used to
easily provide a common header and footer for every page.
=back
template and config files for us! In the 'root' directory, you'll notice
two new directories: src and lib.
-Several configuration files in root/lib/config are called by PRE_PROCESS.
+Several configuration files in F<root/lib/config> are called by C<PRE_PROCESS>.
-The files in root/lib/site are the site-wide templates, called by
-WRAPPER, and display the html framework, control the layout, and provide
+The files in F<root/lib/site> are the site-wide templates, called by
+C<WRAPPER>, and display the html framework, control the layout, and provide
the templates for the header and footer of your page. Using the template
organization provided makes it much easier to standardize pages and make
changes when they are (inevitably) needed.
The template files that you will create for your application will go
-into root/src, and you don't need to worry about putting the <html>
-or <head> sections; just put in the content. The WRAPPER will the rest
+into root/src, and you don't need to worry about putting the C<< <html> >>
+or C<< <head> >> sections; just put in the content. The C<WRAPPER> will the rest
of the page around your template for you.
-=head3 $c->stash
+=head3 C<< $c->stash >>
Of course, having the template system include the header and footer for
you isn't all that we want our templates to do. We need to be able to
$c->forward( $c->view('TT') );
}
-Then, in hello.tt:
+Then, in F<hello.tt>:
<strong>Hello, [% name %]!</strong>
and allows you to truly keep your presentation logic separate from the
rest of your application.
-=head3 $c->uri_for()
+=head3 C<< $c->uri_for() >>
One of my favorite things about Catalyst is the ability to move an
application around without having to worry that everything is going to
break. One of the areas that used to be a problem was with the http
links in your template files. For example, suppose you have an
-application installed at http://www.domain.com/Calendar. The links point
-to "/Calendar", "/Calendar/2005", "/Calendar/2005/10", etc. If you move
-the application to be at http://www.mydomain.com/Tools/Calendar, then
+application installed at C<http://www.domain.com/Calendar>. The links point
+to "C</Calendar>", "C</Calendar/2005>", "C</Calendar/2005/10>", etc. If you move
+the application to be at C<http://www.mydomain.com/Tools/Calendar>, then
all of those links will suddenly break.
-That's where $c->uri_for() comes in. This function will merge its
+That's where C<< $c->uri_for() >> comes in. This function will merge its
parameters with either the base location for the app, or its current
namespace. Let's take a look at a couple of examples.
Although the parameter starts with a forward slash, this is relative
to the application root, not the webserver root. This is important to
remember. So, if your application is installed at
-http://www.domain.com/Calendar, then the link would be
-http://www.mydomain.com/Calendar/Login. If you move your application
+C<http://www.domain.com/Calendar>, then the link would be
+C<http://www.mydomain.com/Calendar/Login>. If you move your application
to a different domain or path, then that link will still be correct.
Likewise,
The first parameter does NOT have a forward slash, and so it will be
relative to the current namespace. If the application is installed at
-http://www.domain.com/Calendar. and if the template is called from
-MyApp::Controller::Display, then the link would become
-http://www.domain.com/Calendar/Display/2005/10/24.
+C<http://www.domain.com/Calendar>. and if the template is called from
+C<MyApp::Controller::Display>, then the link would become
+C<http://www.domain.com/Calendar/Display/2005/10/24>.
If you want to link to a parent uri of your current namespace you can
-prefix the arguments with multiple '../':
+prefix the arguments with multiple 'C<../>':
<a href="[% c.uri_for('../../view', stashed_object.id) %]">User view</a>
Once again, this allows you to move your application around without
having to worry about broken links. But there's something else, as
-well. Since the links are generated by uri_for, you can use the same
+well. Since the links are generated by C<uri_for>, you can use the same
template file by several different controllers, and each controller
will get the links that its supposed to. Since we believe in Don't
Repeat Yourself, this is particularly helpful if you have common
Further Reading:
-L<http://search.cpan.org/perldoc?Catalyst>
+L<Catalyst>
-L<http://search.cpan.org/perldoc?Catalyst%3A%3AView%3A%3ATT>
+L<Catalyst::View::TT>
-L<http://search.cpan.org/perldoc?Template>
+L<Template>
=head2 Adding RSS feeds
the normal view action first to get the objects, then handle the output
differently.
-=head3 Using XML::Feed
+=head3 Using L<XML::Feed>
Assuming we have a C<view> action that populates
-'entries' with some DBIx::Class iterator, the code would look something
+'entries' with some L<DBIx::Class> iterator, the code would look something
like this:
sub rss : Local {
spaces in the filename are handled by the browser.
Put this right before calling C<< $c->res->body >> and your browser
-will download a file named C<Important Orders.csv> instead of
+will download a file named F<Important Orders.csv> instead of
C<export>.
You can also use this to have the browser download content which it
Assume our Controller module starts with the following package declaration:
- package MyApp::Controller::Buckets;
+ package MyApp::Controller::Buckets;
and we are running our application on localhost, port 3000 (the test
server default).
controller namespace, an absolute path will represent an exact
matching URL.
- sub my_handles : Path('handles') { .. }
+ sub my_handles : Path('handles') { .. }
becomes
- http://localhost:3000/buckets/handles
+ http://localhost:3000/buckets/handles
and
- sub my_handles : Path('/handles') { .. }
+ sub my_handles : Path('/handles') { .. }
becomes
- http://localhost:3000/handles
+ http://localhost:3000/handles
See also: L<Catalyst::DispatchType::Path>
name of the action is matched in the URL. The namespaces created by
the name of the controller package is always part of the URL.
- sub my_handles : Local { .. }
+ sub my_handles : Local { .. }
becomes
- http://localhost:3000/buckets/my_handles
+ http://localhost:3000/buckets/my_handles
=item Global
A Global attribute is similar to a Local attribute, except that the
namespace of the controller is ignored, and matching starts at root.
- sub my_handles : Global { .. }
+ sub my_handles : Global { .. }
becomes
- http://localhost:3000/my_handles
+ http://localhost:3000/my_handles
=item Regex
sounds like. This one takes a regular expression, and matches starting
from root. These differ from the rest as they can match multiple URLs.
- sub my_handles : Regex('^handles') { .. }
+ sub my_handles : Regex('^handles') { .. }
matches
- http://localhost:3000/handles
+ http://localhost:3000/handles
and
- http://localhost:3000/handles_and_other_parts
+ http://localhost:3000/handles_and_other_parts
etc.
A LocalRegex is similar to a Regex, except it only matches below the current
controller namespace.
- sub my_handles : LocalRegex(^handles') { .. }
+ sub my_handles : LocalRegex(^handles') { .. }
matches
- http://localhost:3000/buckets/handles
+ http://localhost:3000/buckets/handles
and
- http://localhost:3000/buckets/handles_and_other_parts
+ http://localhost:3000/buckets/handles_and_other_parts
etc.
to create your own internal actions, which can be forwarded to, but
won't be matched as URLs.
- sub my_handles : Private { .. }
+ sub my_handles : Private { .. }
becomes nothing at all..
to find out where it was the user was trying to go, you can look in
the request object using C<< $c->req->path >>.
- sub default :Path { .. }
+ sub default :Path { .. }
works for all unknown URLs, in this controller namespace, or every one
if put directly into MyApp.pm.
actions are defined, then index will be used instead of default and
Path.
- sub index :Path :Args(0) { .. }
+ sub index :Path :Args(0) { .. }
becomes
- http://localhost:3000/buckets
+ http://localhost:3000/buckets
=item begin
app. A single begin action is called, its always the one most relevant
to the current namespace.
- sub begin : Private { .. }
+ sub begin : Private { .. }
is called once when
- http://localhost:3000/bucket/(anything)?
+ http://localhost:3000/bucket/(anything)?
is visited.
always the one most relevant to the current namespace.
- sub end : Private { .. }
+ sub end : Private { .. }
is called once after any actions when
- http://localhost:3000/bucket/(anything)?
+ http://localhost:3000/bucket/(anything)?
is visited.
called. (In contrast, only one of the begin/end/default actions will
be called, the relevant one).
- package MyApp::Controller::Root;
- sub auto : Private { .. }
+ package MyApp::Controller::Root;
+ sub auto : Private { .. }
and
will both be called when visiting
- http://localhost:3000/bucket/(anything)?
+ http://localhost:3000/bucket/(anything)?
=back
=head3 A word of warning
-You can put root actions in your main MyApp.pm file, but this is deprecated,
+You can put root actions in your main F<MyApp.pm> file, but this is deprecated,
please put your actions into your Root controller.
=head3 Flowchart
A graphical flowchart of how the dispatcher works can be found on the wiki at
-L<http://dev.catalyst.perl.org/attachment/wiki/WikiStart/catalyst-flow.png>.
+L<http://dev.catalystframework.org/attachment/wiki/WikiStart/catalyst-flow.png>.
=head2 DRY Controllers with Chained actions
this:
<form action="/upload" method="post" enctype="multipart/form-data">
- <input type="hidden" name="form_submit" value="yes">
- <input type="file" name="my_file">
- <input type="submit" value="Send">
+ <input type="hidden" name="form_submit" value="yes">
+ <input type="file" name="my_file">
+ <input type="submit" value="Send">
</form>
It's very important not to forget C<enctype="multipart/form-data"> in
The form should have this basic structure:
<form action="/upload" method="post" enctype="multipart/form-data">
- <input type="hidden" name="form_submit" value="yes">
- <input type="file" name="file1" size="50"><br>
- <input type="file" name="file2" size="50"><br>
- <input type="file" name="file3" size="50"><br>
- <input type="submit" value="Send">
+ <input type="hidden" name="form_submit" value="yes">
+ <input type="file" name="file1" size="50"><br>
+ <input type="file" name="file2" size="50"><br>
+ <input type="file" name="file3" size="50"><br>
+ <input type="submit" value="Send">
</form>
And in the controller:
C<forward>; in earlier versions, you can manually set the arguments in
the Catalyst Request object:
- # version 5.30 and later:
- $c->forward('/wherever', [qw/arg1 arg2 arg3/]);
+ # version 5.30 and later:
+ $c->forward('/wherever', [qw/arg1 arg2 arg3/]);
- # pre-5.30
- $c->req->args([qw/arg1 arg2 arg3/]);
- $c->forward('/wherever');
+ # pre-5.30
+ $c->req->args([qw/arg1 arg2 arg3/]);
+ $c->forward('/wherever');
(See the L<Catalyst::Manual::Intro> Flow_Control section for more
information on passing arguments via C<forward>.)
=head2 Chained dispatch using base classes, and inner packages.
- package MyApp::Controller::Base;
- use base qw/Catalyst::Controller/;
+ package MyApp::Controller::Base;
+ use base qw/Catalyst::Controller/;
- sub key1 : Chained('/')
+ sub key1 : Chained('/')
=head2 Extending RenderView (formerly DefaultEnd)
method:
sub end : ActionClass('RenderView') {
- my ( $self, $c ) = @_;
- # do stuff here; the RenderView action is called afterwards
+ my ( $self, $c ) = @_;
+ # do stuff here; the RenderView action is called afterwards
}
To add things to an C<end> action that are called I<after> rendering,
sub render : ActionClass('RenderView') { }
sub end : Private {
- my ( $self, $c ) = @_;
- $c->forward('render');
- # do stuff here
+ my ( $self, $c ) = @_;
+ $c->forward('render');
+ # do stuff here
}
Static::Simple is a plugin that will help to serve static content for your
application. By default, it will serve most types of files, excluding some
standard Template Toolkit extensions, out of your B<root> file directory. All
-files are served by path, so if B<images/me.jpg> is requested, then
-B<root/images/me.jpg> is found and served.
+files are served by path, so if F<images/me.jpg> is requested, then
+F<root/images/me.jpg> is found and served.
=head3 Usage
Using the plugin is as simple as setting your use line in MyApp.pm to include:
- use Catalyst qw/Static::Simple/;
+ use Catalyst qw/Static::Simple/;
and already files will be served.
=head3 Configuring
Static content is best served from a single directory within your root
-directory. Having many different directories such as C<root/css> and
-C<root/images> requires more code to manage, because you must separately
-identify each static directory--if you decide to add a C<root/js>
+directory. Having many different directories such as F<root/css> and
+F<root/images> requires more code to manage, because you must separately
+identify each static directory--if you decide to add a F<root/js>
directory, you'll need to change your code to account for it. In
contrast, keeping all static directories as subdirectories of a main
-C<root/static> directory makes things much easier to manage. Here's an
+F<root/static> directory makes things much easier to manage. Here's an
example of a typical root directory structure:
root/
root/static/js/code.js
-All static content lives under C<root/static>, with everything else being
+All static content lives under F<root/static>, with everything else being
Template Toolkit files.
=over 4
You may of course want to change the default locations, and make
Static::Simple look somewhere else, this is as easy as:
- MyApp->config(
- static => {
- include_path => [
- MyApp->path_to('/'),
- '/path/to/my/files',
- ],
- },
- );
+ MyApp->config(
+ static => {
+ include_path => [
+ MyApp->path_to('/'),
+ '/path/to/my/files',
+ ],
+ },
+ );
When you override include_path, it will not automatically append the
normal root path, so you need to add it yourself if you still want
If you want to force some directories to be only static, you can set
them using paths relative to the root dir, or regular expressions:
- MyApp->config(
- static => {
- dirs => [
- 'static',
- qr/^(images|css)/,
- ],
- },
- );
+ MyApp->config(
+ static => {
+ dirs => [
+ 'static',
+ qr/^(images|css)/,
+ ],
+ },
+ );
=item File extensions
be processed by Catalyst): B<tmpl, tt, tt2, html, xhtml>. This list can
be replaced easily:
- MyApp->config(
+ MyApp->config(
static => {
ignore_extensions => [
qw/tmpl tt tt2 html xhtml/
],
},
- );
+ );
=item Ignoring directories
Entire directories can be ignored. If used with include_path,
directories relative to the include_path dirs will also be ignored:
- MyApp->config( static => {
+ MyApp->config( static => {
ignore_dirs => [ qw/tmpl css/ ],
- });
+ });
=back
=head3 More information
-L<http://search.cpan.org/dist/Catalyst-Plugin-Static-Simple/>
+L<Catalyst::Plugin::Static::Simple>
=head3 Serving manually with the Static plugin with HTTP::Daemon (myapp_server.pl)
my ( $self, $c ) = @_;
$c->forward( 'MyApp::View::TT' )
- unless ( $c->res->body || !$c->stash->{template} );
+ unless ( $c->res->body || !$c->stash->{template} );
}
This code will only forward to the view if a template has been
previously defined by a controller and if there is not already data in
-C<$c-E<gt>res-E<gt>body>.
+C<< $c->res->body >>.
Next, create a controller to handle requests for the /static path. Use
the Helper to save time. This command will create a stub controller as
-C<lib/MyApp/Controller/Static.pm>.
+F<lib/MyApp/Controller/Static.pm>.
$ script/myapp_create.pl controller Static
When using Apache, you can bypass Catalyst and any Static
plugins/controllers controller by intercepting requests for the
-C<root/static> path at the server level. All that is required is to
+F<root/static> path at the server level. All that is required is to
define a DocumentRoot and add a separate Location block for your static
content. Here is a complete config for this application under mod_perl
1.x:
L<Catalyst::Plugin::Cache::FileCache>
L<Catalyst::Plugin::Cache::Memcached>
L<Catalyst::Plugin::PageCache>
-L<http://search.cpan.org/dist/Template-Toolkit/lib/Template/Manual/Config.pod#Caching_and_Compiling_Options>
+L<Template::Manual::Config/Caching and Compiling Options>
=head1 Testing
Catalyst provides a convenient way of testing your application during
development and before deployment in a real environment.
-C<Catalyst::Test> makes it possible to run the same tests both locally
+L<Catalyst::Test> makes it possible to run the same tests both locally
(without an external daemon) and against a remote server via HTTP.
=head3 Tests
-Let's examine a skeleton application's C<t/> directory:
+Let's examine a skeleton application's F<t/> directory:
mundus:~/MyApp chansen$ ls -l t/
total 24
=over 4
-=item C<01app.t>
+=item F<01app.t>
Verifies that the application loads, compiles, and returns a successful
response.
-=item C<02pod.t>
+=item F<02pod.t>
Verifies that all POD is free from errors. Only executed if the C<TEST_POD>
environment variable is true.
-=item C<03podcoverage.t>
+=item F<03podcoverage.t>
Verifies that all methods/functions have POD coverage. Only executed if the
C<TEST_POD> environment variable is true.
two. The second line tests and loads our application in test mode. The
fourth line verifies that our application returns a successful response.
-C<Catalyst::Test> exports two functions, C<request> and C<get>. Each can
+L<Catalyst::Test> exports two functions, C<request> and C<get>. Each can
take three different arguments:
=over 4
request('/my/path');
request('http://www.host.com/my/path');
-=item An instance of C<URI>.
+=item An instance of L<URI>.
request( URI->new('http://www.host.com/my/path') );
-=item An instance of C<HTTP::Request>.
+=item An instance of L<HTTP::Request>.
request( HTTP::Request->new( GET => 'http://www.host.com/my/path') );
=back
-C<request> returns an instance of C<HTTP::Response> and C<get> returns the
+C<request> returns an instance of L<HTTP::Response> and C<get> returns the
content (body) of the response.
=head3 Running tests locally
your application. In C<CGI> or C<FastCGI> it should be the host and path
to the script.
-=head3 C<Test::WWW::Mechanize> and Catalyst
+=head3 L<Test::WWW::Mechanize> and Catalyst
-Be sure to check out C<Test::WWW::Mechanize::Catalyst>. It makes it easy to
+Be sure to check out L<Test::WWW::Mechanize::Catalyst>. It makes it easy to
test HTML, forms and links. A short example of usage:
use Test::More tests => 6;
=over 4
-=item Catalyst::Test
-
-L<Catalyst::Test>
-
-=item Test::WWW::Mechanize::Catalyst
-
-L<http://search.cpan.org/dist/Test-WWW-Mechanize-Catalyst/lib/Test/WWW/Mechanize/Catalyst.pm>
-
-=item Test::WWW::Mechanize
-
-L<http://search.cpan.org/dist/Test-WWW-Mechanize/Mechanize.pm>
-
-=item WWW::Mechanize
-
-L<http://search.cpan.org/dist/WWW-Mechanize/lib/WWW/Mechanize.pm>
-
-=item LWP::UserAgent
+=item * L<Catalyst::Test>
-L<http://search.cpan.org/dist/libwww-perl/lib/LWP/UserAgent.pm>
+=item * L<Test::WWW::Mechanize::Catalyst>
-=item HTML::Form
+=item * L<Test::WWW::Mechanize>
-L<http://search.cpan.org/dist/libwww-perl/lib/HTML/Form.pm>
+=item * L<WWW::Mechanize>
-=item HTTP::Message
+=item * L<LWP::UserAgent>
-L<http://search.cpan.org/dist/libwww-perl/lib/HTTP/Message.pm>
+=item * L<HTML::Form>
-=item HTTP::Request
+=item * L<HTTP::Message>
-L<http://search.cpan.org/dist/libwww-perl/lib/HTTP/Request.pm>
+=item * L<HTTP::Request>
-=item HTTP::Request::Common
+=item * L<HTTP::Request::Common>
-L<http://search.cpan.org/dist/libwww-perl/lib/HTTP/Request/Common.pm>
+=item * L<HTTP::Response>
-=item HTTP::Response
+=item * L<HTTP::Status>
-L<http://search.cpan.org/dist/libwww-perl/lib/HTTP/Response.pm>
+=item * L<URI>
-=item HTTP::Status
+=item * L<Test::More>
-L<http://search.cpan.org/dist/libwww-perl/lib/HTTP/Status.pm>
+=item * L<Test::Pod>
-=item URI
+=item * L<Test::Pod::Coverage>
-L<http://search.cpan.org/dist/URI/URI.pm>
+=item * L<prove> (L<Test::Harness>)
-=item Test::More
-
-L<http://search.cpan.org/dist/Test-Simple/lib/Test/More.pm>
-
-=item Test::Pod
-
-L<http://search.cpan.org/dist/Test-Pod/Pod.pm>
+=back
-=item Test::Pod::Coverage
+=head3 More Information
-L<http://search.cpan.org/dist/Test-Pod-Coverage/Coverage.pm>
+=over 4
-=item prove (Test::Harness)
+=item * L<Catalyst::Plugin::Authorization::Roles>
-L<http://search.cpan.org/dist/Test-Harness/bin/prove>
+=item * L<Catalyst::Plugin::Authorization::ACL>
=back
-=head3 More Information
-
-L<http://search.cpan.org/perldoc?Catalyst::Plugin::Authorization::Roles>
-L<http://search.cpan.org/perldoc?Catalyst::Plugin::Authorization::ACL>
-
=head1 AUTHORS
Catalyst Contributors, see Catalyst.pm
=head2 Chef
-L<Chef|http://www.opscode.com/chef/> is an open-source systems integration
+L<Chef|https://www.chef.io/products/chef-infra/> is an open-source systems integration
framework built specifically for automating cloud computing deployments. A
Cookbooks demonstrating how to deploy a Catalyst application using Chef is
-available at L<http://community.opscode.com/cookbooks/catalyst> and
-L<http://github.com/melezhik/cookbooks/wiki/Catalyst-cookbook-intro>.
+available at L<https://supermarket.chef.io/cookbooks/catalyst> and
+L<https://github.com/melezhik/cookbooks/wiki/Catalyst-cookbook-intro>.
=head1 AUTHORS
=head3 1. Install Apache with mod_fastcgi
mod_fastcgi for Apache is a third-party module, and can be found at
-L<http://www.fastcgi.com/>. It is also packaged in many distributions
+L<https://fastcgi-archives.github.io/>. It is also packaged in many distributions
(for example, libapache2-mod-fastcgi in Debian). You will also need to
install the L<FCGI> module from CPAN.
application.
For more information on using FastCGI under Apache, visit
-L<http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html>
+L<https://fastcgi-archives.github.io/mod_fastcgi.html>
=head3 Authorization header with mod_fastcgi or mod_cgi
By default, mod_fastcgi/mod_cgi do not pass along the Authorization header,
-so modules like C<Catalyst::Plugin::Authentication::Credential::HTTP> will
+so modules like L<Catalyst::Plugin::Authentication::Credential::HTTP> will
not work. To enable pass-through of this header, add the following
mod_rewrite directives:
use lib '/var/www/MyApp/lib';
use MyApp;
</Perl>
-
+
<Location />
SetHandler modperl
PerlResponseHandler MyApp
=head2 Other web servers
The proxy configuration above can also be replicated with a different
-frontend server or proxy, such as varnish, nginx, or lighttpd.
+frontend server or proxy, such as varnish, nginx, or lighttpd.
=head1 AUTHORS
FastCGI is not a standard part of IIS 6 - you have to install it
separately. For more info and the download, go to
-L<http://www.iis.net/extensions/FastCGI>. Choose the appropriate version
+Lhttps://www.iis.net/downloads/microsoft/fastcgi-for-iis>. Choose the appropriate version
(32-bit/64-bit); installation is quite simple (in fact no questions, no
options).
RewriteRule ^(.*)$ script/myapp_fastcgi.pl/$1 [PT,L]
Now C<http://mydomain.com/> should now Just Work. Congratulations, now
-you can tell your friends about your new website.
+you can tell your friends about your new website.
=head1 AUTHORS
)
For more information on using FastCGI under Lighttpd, visit
-L<http://www.lighttpd.net/documentation/fastcgi.html>
+L<https://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModFastCGI>
=head2 Static file handling
=head1 MORE INFO
For more information on nginx, visit:
-L<http://nginx.net>
+L<https://nginx.net>
=head1 AUTHORS
write: catagits@git.shadowcat.co.uk:PROJECTNAME
browser: https://git.shadowcat.co.uk/gitweb/gitweb.cgi
-The Catalyst subversion repository can be found at:
-
- svn: http://dev.catalyst.perl.org/repos/Catalyst
- browser: http://dev.catalyst.perl.org/svnweb/Catalyst
-
=head2 Schedule
There is no dated release cycle for Catalyst. New releases will be made
when sufficient small fixes have accumulated; or an important bugfix, or
significant feature addition, is completed.
-=head2 Roadmap for features
-
-The Catalyst Roadmap is kept at
-L<http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits/Catalyst-Runtime.git;a=blob_plain;f=lib/Catalyst/ROADMAP.pod;hb=master>
-
-=head2 Bug list
-
-The TODO list with known bugs / deficiencies is kept at
-L<http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits/Catalyst-Runtime.git;a=blob_plain;f=TODO;hb=master>
-
=head1 The Catalyst Core Team
The intention of the Catalyst Core Team is to maintain and support the
the same terms as Perl itself.
=cut
-
written to help you understand the possibilities, current practices
and their consequences.
-Please read the L<BEST PRACTICES> section before deciding on a design,
+Please read the L</BEST PRACTICES> section before deciding on a design,
especially if you plan to release your code to CPAN. The Catalyst
developer and user communities, which B<you are part of>, will benefit
most if we all work together and coordinate.
This gives a stable basis for contribution, and even more importantly,
builds trust. The easiest way is a test application. See
-L<Catalyst::Manual::Tutorial::Testing> for more information.
+L<Catalyst::Manual::Tutorial::Testing|Catalyst::Manual::Tutorial::08_Testing>
+for more information.
=back
Writing a generic component that only works with Catalyst is wasteful
of your time. Try writing a plain perl module, and then a small bit
of glue that integrates it with Catalyst. See
-L<Catalyst::Model::DBIC::Schema|Catalyst::Model::DBIC::Schema> for a
+L<Catalyst::Model::DBIC::Schema> for a
module that takes the approach. The advantage here is that your
"Catalyst" DBIC schema works perfectly outside of Catalyst, making
testing (and command-line scripts) a breeze. The actual Catalyst
convenient.
If you want the thinnest interface possible, take a look at
-L<Catalyst::Model::Adaptor|Catalyst::Model::Adaptor>.
+L<Catalyst::Model::Adaptor>.
=head2 Using Moose roles to apply method modifiers
invaluable.
If you're just getting started, try using
-L<CatalystX::Starter|CatalystX::Starter> to generate some example
+L<CatalystX::Starter> to generate some example
tests for your module.
=head2 Maintenance
You can specify any valid Perl attribute on Catalyst actions you like.
(See L<attributes/"Syntax of Attribute Lists"> for a description of
-what is valid.) These will be available on the C<Catalyst::Action>
+what is valid.) These will be available on the L<Catalyst::Action>
instance via its C<attributes> accessor. To give an example, this
action:
=head2 Component Configuration
At creation time, the class configuration of your component (the one
-available via C<$self-E<gt>config>) will be merged with possible
+available via C<< $self->config >>) will be merged with possible
configuration settings from the applications configuration (either
directly or via config file). This is done by Catalyst, and the
correctly merged configuration is passed to your component's
accessor.
The C<config> accessor always only contains the original class configuration
-and you B<MUST NEVER> call $self->config to get your component configuration,
+and you B<MUST NEVER> call C<< $self->config >> to get your component configuration,
as the data there is likely to be a subset of the correct config.
For example:
methods code. You can surround this by overriding the method in a
subclass:
- package Catalyst::Action::MyFoo;
+ package Catalyst::Action::MyFoo;
use Moose;
use namespace::autoclean;
- use MRO::Compat;
+ use MRO::Compat;
extends 'Catalyst::Action';
sub execute {
1;
We are using L<MRO::Compat> to ensure that you have the next::method
-call, from L<Class::C3> (in older perls), or natively (if you are using
-perl 5.10) to re-dispatch to the original C<execute> method in the
+call, from L<Class::C3> (in older perls), or natively (if you are using
+perl 5.10) to re-dispatch to the original C<execute> method in the
L<Catalyst::Action> class.
The Catalyst dispatcher handles an incoming request and, depending
-upon the dispatch type, will call the appropriate target or chain.
+upon the dispatch type, will call the appropriate target or chain.
From time to time it asks the actions themselves, or through the
controller, if they would match the current request. That's what the
C<match> method does. So by overriding this, you can change on what
For example, the action class below will make the action only match on
Mondays:
- package Catalyst::Action::OnlyMondays;
+ package Catalyst::Action::OnlyMondays;
use Moose;
use namespace::autoclean;
use MRO::Compat;
package MyApp::Controller::Foo;
use Moose;
use namespace::autoclean;
-
+
BEGIN { extends 'MyApp::Base::Controller::ModelBase'; }
__PACKAGE__->config( model_name => 'DB::Foo',
package Catalyst::View::MyView;
use Moose;
use namespace::autoclean;
-
+
extends 'Catalyst::View';
sub process {
C<finalize_*> stages, you won't get around a plugin.
Note, if you just want to hook into such a stage, and run code before,
-or after it, then it is recommended that you use L<Moose>s method modifiers
+or after it, then it is recommended that you use L<Moose>'s method modifiers
to do this.
Another valid target for a plugin architecture are things that
if (!blessed($_[0]) || !$_[0]->isa('Catalyst::Action'));
return $uri;
};
-
+
Note that Catalyst will load any Moose Roles in the plugin list,
and apply them to your application class.
package CatalystX::Component::Foo;
use Moose;
use namespace::autoclean;
-
+
extends 'Catalyst::Component';
sub COMPONENT {
my $class = shift;
# Note: $app is like $c, but since the application isn't fully
- # initialized, we don't want to call it $c yet. $config
+ # initialized, we don't want to call it $c yet. $config
# is a hashref of config options possibly set on this component.
my ($app, $config) = @_;
the same terms as Perl itself.
=cut
-
-
When the Catalyst module is imported in the main application
module, it stores any options.
-
=item 2
When C<< __PACKAGE__->setup >> is called, it evaluates any
Catalyst automatically loads all
components it finds in the C<$class::Controller>, C<$class::C>,
C<$class::Model>, C<$class::M>, C<$class::View> and C<$class::V>
-namespaces (using C<Module::Pluggable>). As each is loaded, if it has a
+namespaces (using L<Module::Pluggable>). As each is loaded, if it has a
L<COMPONENT|Catalyst::Component/"COMPONENT"> method then this method
will be called, and passed that component's configuration. It then returns
an instance of the component, which becomes the C<$self> when methods in
Each controller has it's C<register_actions> method called. At this point,
the subroutine attributes are retrieved from the
-L<MooseX::MethodAttributes::Role::Meta::Map|metaclass>, parsed, and used to
+L<metaclass|MooseX::MethodAttributes::Role::Meta::Map>, parsed, and used to
build instances of L<Catalyst::Action>, which are then registered with
the dispatcher.
Catalyst is an elegant web application framework, extremely flexible
yet extremely simple. It's similar to Ruby on Rails, Spring (Java), and
-L<Maypole|Maypole>, upon which it was originally based. Its most
+L<Maypole>, upon which it was originally based. Its most
important design philosophy is to provide easy access to all the tools
you need to develop web applications, with few restrictions on how you
need to use these tools. However, this does mean that it is always
Images (AMI) that include all the elements you'd need to begin
developing in a fully functional Catalyst environment within
minutes. See
-L<Catalyst::Manual::Installation|Catalyst::Manual::Installation> for
+L<Catalyst::Manual::Installation> for
more details.
be C<TT>, and the second that it should be a Template Toolkit view.)
This gives us a process() method and we can now just do
-$c->forward('MyApp::View::TT') to render our templates. The base class
+C<< $c->forward('MyApp::View::TT') >> to render our templates. The base class
makes process() implicit, so we don't have to say
-C<$c-E<gt>forward(qw/MyApp::View::TT process/)>.
+C<< $c->forward(qw/MyApp::View::TT process/) >>.
sub hello : Global {
my ( $self, $c ) = @_;
by L<Catalyst::Action::RenderView>.
Also, be sure to put the template under the directory specified in
-C<$c-E<gt>config-E<gt>{root}>, or you'll end up looking at the debug
+C<< $c->config->{root} >>, or you'll end up looking at the debug
screen.
=head4 Models
will find and load it automatically at compile-time; you can
C<forward> to the module, which can only be done to Catalyst
components. Only Catalyst components can be fetched with
-C<$c-E<gt>model('SomeModel')>.
+C<< $c->model('SomeModel') >>.
Happily, since many people have existing Model classes that they
would like to use with Catalyst (or, conversely, they want to
=head3 ACCEPT_CONTEXT
-Whenever you call $c->component("Foo") you get back an object - the
+Whenever you call C<< $c->component("Foo") >> you get back an object - the
instance of the model. If the component supports the C<ACCEPT_CONTEXT>
method instead of returning the model itself, the return value of C<<
$model->ACCEPT_CONTEXT( $c ) >> will be used.
Note that we still want the Catalyst models to be a thin wrapper
around classes that will work independently of the Catalyst
application to promote reusability of code. Here we might just want
-to grab the $c->model('DB')->schema so as to get the connection
+to grab the C<< $c->model('DB')->schema >> so as to get the connection
information from the Catalyst application's configuration for example.
The life time of this value is B<per usage>, and not per request. To
data. If omitted, Catalyst will try to auto-detect the directory's
location. You can define as many parameters as you want for plugins or
whatever you need. You can access them anywhere in your application via
-C<$context-E<gt>config-E<gt>{$param_name}>.
+C<< $context->config->{$param_name} >>.
=head3 Context
For both C<:LocalRegex> and C<:Regex> actions, if you use capturing
parentheses to extract values within the matching URL, those values
-are available in the C<$c-E<gt>req-E<gt>captures> array. In the above
+are available in the C<< $c->req->captures >> array. In the above
example, "widget23" would capture "23" in the above example, and
-C<$c-E<gt>req-E<gt>captures-E<gt>[0]> would be "23". If you want to
+C<< $c->req->captures->[0] >> would be "23". If you want to
pass arguments at the end of your URL, you must use regex action
keys. See L</URL Path Handling> below.
the method, so that a private C<bar> method in your
C<MyApp::Controller::Catalog::Order::Process> controller must, if
called from elsewhere, be reached with
-C<$c-E<gt>forward('/catalog/order/process/bar')>.
+C<< $c->forward('/catalog/order/process/bar') >>.
=back
Parameters passed in the URL query string are handled with methods in
the L<Catalyst::Request> class. The C<param> method is functionally
-equivalent to the C<param> method of C<CGI.pm> and can be used in
+equivalent to the C<param> method of L<CGI.pm|CGI> and can be used in
modules that require this.
# http://localhost:3000/catalog/view/?category=hardware&page=3
}
A C<forward> does not create a new request, so your request object
-(C<$c-E<gt>req>) will remain unchanged. This is a key difference between
+(C<< $c->req >>) will remain unchanged. This is a key difference between
using C<forward> and issuing a redirect.
You can pass new arguments to a C<forward> by adding them
-in an anonymous array. In this case C<$c-E<gt>req-E<gt>args>
+in an anonymous array. In this case C<< $c->req->args >>
will be changed for the duration of the C<forward> only; upon
-return, the original value of C<$c-E<gt>req-E<gt>args> will
+return, the original value of C<< $c->req->args >> will
be reset.
sub hello : Global {
$c->stash->{message} = 'Hello World!';
$self->check_message( $c, 'test1' );
}
-
+
sub check_message {
my ( $self, $c, $first_argument ) = @_;
# do something...
We suggest that you read this introduction on the web. Make sure you are
reading the latest version of the tutorial by visiting
-L<https://metacpan.org/module/Catalyst::Manual::Tutorial>. Alternatively
+L<Catalyst::Manual::Tutorial>. Alternatively
you can use CPAN modules like L<Pod::Webserver>, L<Pod::POM::Web>,
L<Pod::Browser> (Catalyst based), or L<CPAN::Mini::Webserver> to read a local
copy of the tutorial.
=back
-Final code tarballs for each chapter of the tutorial are available at
-L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial/>.
+Final code tarballs for each chapter of the tutorial are available at
+L<http://dev.catalystframework.org/repos/Catalyst/trunk/examples/Tutorial/>.
=head1 Detailed Table of Contents
=head2 L<Chapter 1: Intro|Catalyst::Manual::Tutorial::01_Intro>
-Note: Click on the heading in the previous line to jump to the actual
+Note: Click on the heading in the previous line to jump to the actual
chapter. Below is a "table of contents" for this chapter.
=over 4
=head2 L<Chapter 2: Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics>
-Note: Click on the heading in the previous line to jump to the actual
+Note: Click on the heading in the previous line to jump to the actual
chapter. Below is a "table of contents" for this chapter.
=over 4
=head2 L<Chapter 3: More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
-Note: Click on the heading in the previous line to jump to the actual
+Note: Click on the heading in the previous line to jump to the actual
chapter. Below is a "table of contents" for this chapter.
=over 4
=head2 L<Chapter 4: Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
-Note: Click on the heading in the previous line to jump to the actual
+Note: Click on the heading in the previous line to jump to the actual
chapter. Below is a "table of contents" for this chapter.
=over 4
=head2 L<Chapter 5: Authentication|Catalyst::Manual::Tutorial::05_Authentication>
-Note: Click on the heading in the previous line to jump to the actual
+Note: Click on the heading in the previous line to jump to the actual
chapter. Below is a "table of contents" for this chapter.
=over 4
=head2 L<Chapter 6: Authorization|Catalyst::Manual::Tutorial::06_Authorization>
-Note: Click on the heading in the previous line to jump to the actual
+Note: Click on the heading in the previous line to jump to the actual
chapter. Below is a "table of contents" for this chapter.
=over 4
=head2 L<Chapter 7: Debugging|Catalyst::Manual::Tutorial::07_Debugging>
-Note: Click on the heading in the previous line to jump to the actual
+Note: Click on the heading in the previous line to jump to the actual
chapter. Below is a "table of contents" for this chapter.
=over 4
=head2 L<Chapter 8: Testing|Catalyst::Manual::Tutorial::08_Testing>
-Note: Click on the heading in the previous line to jump to the actual
+Note: Click on the heading in the previous line to jump to the actual
chapter. Below is a "table of contents" for this chapter.
=over 4
=head2 L<Chapter 9: Advanced CRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD>
-Note: Click on the heading in the previous line to jump to the actual
+Note: Click on the heading in the previous line to jump to the actual
chapter. Below is a "table of contents" for this chapter.
=over 4
=head2 L<Chapter 10: Appendices|Catalyst::Manual::Tutorial::10_Appendices>
-Note: Click on the heading in the previous line to jump to the actual
+Note: Click on the heading in the previous line to jump to the actual
chapter. Below is a "table of contents" for this chapter.
=over 4
=head1 THANKS
-This tutorial would not have been possible without the input of many
-different people in the Catalyst community. In particular, the
+This tutorial would not have been possible without the input of many
+different people in the Catalyst community. In particular, the
primary author would like to thank:
=over 4
Other Catalyst documentation folks like Kieren Diment, Gavin Henry,
and Jess Robinson (including their work on the original Catalyst
-tutorial).
+tutorial).
=item *
=item *
-People who have emailed me with corrections and suggestions on the
-tutorial. As of the most recent release, this include: Florian Ragwitz,
-Mauro Andreolini, Jim Howard, Giovanni Gigante, William Moreno, Bryan
-Roach, Ashley Berlin, David Kamholz, Kevin Old, Henning Sprang, Jeremy
-Jones, David Kurtz, Ingo Wichmann, Shlomi Fish, Murray Walker, Adam
-Witney and xenoterracide (Caleb Cushing). Thanks to Devin Austin for
-coming up with an initial version of a non-TTSite wrapper page. Also, a
-huge thank you to Kiffin Gish for all the hard work on the "database
-depluralization" effort and Rafael Kitover for the work on updating the
-tutorial to include foreign key support for SQLite. I'm sure I am
-missing some names here... apologies for that (please let me know if you
-name should be here).
+People who have emailed me with corrections and suggestions on the
+tutorial. As of the most recent release, this include: Florian Ragwitz,
+Mauro Andreolini, Jim Howard, Giovanni Gigante, William Moreno, Bryan
+Roach, Ashley Berlin, David Kamholz, Kevin Old, Henning Sprang, Jeremy
+Jones, David Kurtz, Ingo Wichmann, Shlomi Fish, Murray Walker, Adam
+Witney and xenoterracide (Caleb Cushing). Thanks to Devin Austin for
+coming up with an initial version of a non-TTSite wrapper page. Also, a
+huge thank you to Kiffin Gish for all the hard work on the "database
+depluralization" effort and Rafael Kitover for the work on updating the
+tutorial to include foreign key support for SQLite. I'm sure I am
+missing some names here... apologies for that (please let me know if you
+name should be here).
=back
Copyright 2006-2010, Kennedy Clark, under the
Creative Commons Attribution Share-Alike License Version 3.0
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
+(L<https://creativecommons.org/licenses/by-sa/3.0/us/>).
If you would prefer to install directly from CPAN and not use the
Tutorial Virtual machine, you can download the example program and all
the necessary dependencies to your local machine by installing the
-C<Task::Catalyst::Tutorial> distribution:
+L<Task::Catalyst::Tutorial> distribution:
cpan Task::Catalyst::Tutorial
=over 4
-=item *
+=item *
A simple application that lists and adds books.
some of the more advanced techniques you will probably want to use in
your applications).
-=item *
+=item *
How to write CRUD (Create, Read, Update, and Delete) operations in
Catalyst.
Authentication ("auth").
-=item *
+=item *
Role-based authorization ("authz").
-=item *
+=item *
Attempts to provide an example showing current (5.9) Catalyst
practices.
-=item *
+=item *
The use of Template Toolkit (TT).
-=item *
+=item *
Useful techniques for troubleshooting and debugging Catalyst
applications.
-=item *
+=item *
The use of SQLite as a database (with code also provided for MySQL and
PostgreSQL). (Note: Because we make use of the DBIx::Class Object
agnostic and can easily be used by any of the databases supported by
DBIx::Class.)
-=item *
+=item *
The use of L<HTML::FormFu> or L<HTML::FormHandler>
for automated form processing and validation.
=over 4
-=item 1
+=item 1
Download a Tutorial Virtual Machine image from
L<http://cattut.shadowcat.co.uk/>
=item 3
Boot the virtual machine using a tool like VMWare Player
-L<http://www.vmware.com/products/player> or VirtualBox
-L<http://www.virtualbox.org/>.
+L<https://www.vmware.com/products/workstation-player.html> or VirtualBox
+L<https://www.virtualbox.org/>.
=item 4
Note that C<iceweasel> is basically used to install Firefox on Debian
boxes. You can start it under X Windows with either the C<firefox>
command or the C<iceweasel> command (or use the menus). You can get
-more information on Iceweasel at L<http://wiki.debian.org/Iceweasel>.
+more information on Iceweasel at L<https://wiki.debian.org/Iceweasel>.
Also, you might need to add more memory to your virtual machine if you
want to run X Windows (or other tools that might require additional
=item *
-L<http://vmfaq.com/index.php?View=entry&EntryID=34>
+L<https://web.archive.org/web/20160623183717/http://vmfaq.com/index.php?View=entry&EntryID=34>
=item *
-L<http://www.vmware.com/support/pubs/player_pubs.html>
+L<https://www.vmware.com/support/pubs/player_pubs.html>
=item *
-L<http://www.virtualbox.org/manual/ch06.html>
+L<https://www.virtualbox.org/manual/ch06.html>
=back
=over 4
-=item *
+=item *
Debian 6 (Squeeze)
-=item *
+=item *
Catalyst v5.90002
Catalyst::Devel v1.34
-=item *
+=item *
DBIx::Class v0.08195
HTML::FormFu -- v0.09004
-=item *
+=item *
B<NOTE:> You can check the versions you have installed with the
following command (note the slash before the space):
perl -MCatalyst::Devel -e 'print "$Catalyst::Devel::VERSION\n";'
-=item *
+=item *
This tutorial will show URLs in the format of C<http://localhost:3000>,
but if you are running your web browser from outside the Tutorial
B<Please Note:> Depending on the web browser you are using, you might
need to hit C<Shift+Reload> or C<Ctrl+Reload> to pull a fresh page when
testing your application at various points (see
-L<http://en.wikipedia.org/wiki/Wikipedia:Bypass_your_cache> for a
+L<https://en.wikipedia.org/wiki/Wikipedia:Bypass_your_cache> for a
comprehensive list of options for each browser).
Also, the C<-k> B<keepalive option> to the development server can be
Copyright 2006-2011, Kennedy Clark, under the
Creative Commons Attribution Share-Alike License Version 3.0
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
+(L<https://creativecommons.org/licenses/by-sa/3.0/us/>).
Catalyst provides a number of helper scripts that can be used to quickly
flesh out the basic structure of your application. All Catalyst projects
-begin with the C<catalyst.pl> helper (see
+begin with the F<catalyst.pl> helper (see
L<Catalyst::Helper> for more information on helpers).
Also note that as of Catalyst 5.7000, you will not have the helper
scripts unless you install both L<Catalyst::Runtime>
and L<Catalyst::Devel>.
-In this first chapter of the tutorial, use the Catalyst C<catalyst.pl>
+In this first chapter of the tutorial, use the Catalyst F<catalyst.pl>
script to initialize the framework for an application called C<Hello>:
$ catalyst.pl Hello
from the end of the "catalyst.pl" command and simply use
"catalyst Hello".
-The C<catalyst.pl> helper script will display the names of the
+The F<catalyst.pl> helper script will display the names of the
directories and files it creates:
Changes # Record of application changes
lib # Lib directory for your app's Perl modules
Hello # Application main code directory
- Controller # Directory for Controller modules
+ Controller # Directory for Controller modules
Model # Directory for Models
View # Directory for Views
Hello.pm # Base application module
hello_server.pl # The normal development server
hello_test.pl # Test your app from the command line
t # Directory for tests
- 01app.t # Test scaffold
- 02pod.t
- 03podcoverage.t
+ 01app.t # Test scaffold
+ 02pod.t
+ 03podcoverage.t
Catalyst will "auto-discover" modules in the Controller, Model, and View
-directories. When you use the C<hello_create.pl> script it will create Perl
+directories. When you use the F<hello_create.pl> script it will create Perl
module scaffolds in those directories, plus test files in the "t"
directory. The default location for templates is in the "root"
directory. The scripts in the script directory will always start with
.----------------------------------------------------------------------------.
| Catalyst::Plugin::ConfigLoader 0.30 |
'----------------------------------------------------------------------------'
-
+
[debug] Loaded dispatcher "Catalyst::Dispatcher"
[debug] Loaded engine "Catalyst::Engine"
[debug] Found home "/home/catalyst/Hello"
+-----------------------------------------------------------------+----------+
| Hello::Controller::Root | instance |
'-----------------------------------------------------------------+----------'
-
+
[debug] Loaded Private actions:
.----------------------+--------------------------------------+--------------.
| Private | Class | Method |
| /end | Hello::Controller::Root | end |
| /index | Hello::Controller::Root | index |
'----------------------+--------------------------------------+--------------'
-
+
[debug] Loaded Path actions:
.-------------------------------------+--------------------------------------.
| Path | Private |
| / | /index |
| / | /default |
'-------------------------------------+--------------------------------------'
-
+
[info] Hello powered by Catalyst 5.90002
HTTP::Server::PSGI: Accepting connections at http://0:3000/
=head2 The Simplest Way
The Root.pm controller is a place to put global actions that usually
-execute on the root URL. Open the C<lib/Hello/Controller/Root.pm> file
+execute on the root URL. Open the F<lib/Hello/Controller/Root.pm> file
in your editor. You will see the "index" subroutine, which is
responsible for displaying the welcome screen that you just saw in your
browser.
sub index :Path :Args(0) {
my ( $self, $c ) = @_;
-
+
# Hello World
$c->response->body( $c->welcome_message );
}
L<Catalyst::Runtime>, L<Catalyst::Response>, and
L<Catalyst::Request>)
-C<$c-E<gt>response-E<gt>body> sets the HTTP response (see
+C<< $c->response->body >> sets the HTTP response (see
L<Catalyst::Response>), while
-C<$c-E<gt>welcome_message> is a special method that returns the welcome
+C<< $c->welcome_message >> is a special method that returns the welcome
message that you saw in your browser.
-The ":Path :Args(0)" after the method name are attributes which
+The "C<:Path :Args(0)>" after the method name are attributes which
determine which URLs will be dispatched to this method. (You might see
":Private" if you are using an older version of Catalyst, but using that
with "default" or "index" is currently deprecated. If so, you should
methods. There is a lot of flexibility in specifying which URLs to
match. This particular method will match all URLs, because it doesn't
specify the path (nothing comes after "Path"), but will only accept a
-URL without any args because of the ":Args(0)".
+URL without any args because of the "C<:Args(0)>".
The default is to map URLs to controller names, and because of the way
that Perl handles namespaces through package names, it is simple to
While you leave the C<script/hello_server.pl -r> command running the
development server in one window (don't forget the "-r" at the end!),
open another window and add the following subroutine to your
-C<lib/Hello/Controller/Root.pm> file:
+F<lib/Hello/Controller/Root.pm> file:
sub hello :Global {
my ( $self, $c ) = @_;
-
+
$c->response->body("Hello, World!");
}
Saw changes to the following files:
- /home/catalyst/Hello/lib/Hello/Controller/Root.pm (modify)
-
+
Attempting to restart the server
...
[debug] Loaded Private actions:
$ script/hello_create.pl view HTML TT
-This creates the C<lib/Hello/View/HTML.pm> module, which is a subclass
-of C<Catalyst::View::TT>.
+This creates the F<lib/Hello/View/HTML.pm> module, which is a subclass
+of L<Catalyst::View::TT>.
=over 4
=back
-If you look at C<lib/Hello/View/HTML.pm> you will find that it only
+If you look at F<lib/Hello/View/HTML.pm> you will find that it only
contains a config statement to set the TT extension to ".tt".
Now that the HTML.pm "View" exists, Catalyst will autodiscover it and be
able to use it to display the view templates using the "process" method
-that it inherits from the C<Catalyst::View::TT> class.
+that it inherits from the L<Catalyst::View::TT> class.
Template Toolkit is a very full-featured template facility, with
excellent documentation at L<http://template-toolkit.org/>, but since
explore some of the more common TT features in later chapters of the
tutorial).
-Create a C<root/hello.tt> template file (put it in the C<root> under the
+Create a F<root/hello.tt> template file (put it in the C<root> under the
C<Hello> directory that is the base of your application). Here is a
simple sample:
[% and %] are markers for the TT parts of the template. Inside you can
access Perl variables and classes, and use TT directives. In this case,
we're using a special TT variable that defines the name of the template
-file (C<hello.tt>). The rest of the template is normal HTML.
+file (F<hello.tt>). The rest of the template is normal HTML.
-Change the hello method in C<lib/Hello/Controller/Root.pm> to the
+Change the hello method in F<lib/Hello/Controller/Root.pm> to the
following:
sub hello :Global {
my ( $self, $c ) = @_;
-
+
$c->stash(template => 'hello.tt');
}
-This time, instead of doing C<$c-E<gt>response-E<gt>body()>, you are
+This time, instead of doing C<< $c->response->body() >>, you are
setting the value of the "template" hash key in the Catalyst "stash", an
area for putting information to share with other parts of your
application. The "template" key determines which template will be
displayed at the end of the request cycle. Catalyst controllers have a
default "end" action for all methods which causes the first (or default)
-view to be rendered (unless there's a C<$c-E<gt>response-E<gt>body()>
+view to be rendered (unless there's a C<< $c->response->body() >>
statement). So your template will be magically displayed at the end of
your method.
used previous is becoming more common because it allows you to
set multiple stash variables in one line. For example:
- $c->stash(template => 'hello.tt', foo => 'bar',
+ $c->stash(template => 'hello.tt', foo => 'bar',
another_thing => 1);
You can also set multiple stash values with a hashref:
- $c->stash({template => 'hello.tt', foo => 'bar',
+ $c->stash({template => 'hello.tt', foo => 'bar',
another_thing => 1});
-Any of these formats work, but the C<$c-E<gt>stash(name =E<gt> value);>
+Any of these formats work, but the C<< $c->stash(name => value); >>
style is growing in popularity -- you may wish to use it all the time
(even when you are only setting a single value).
$ script/hello_create.pl controller Site
-This will create a C<lib/Hello/Controller/Site.pm> file (and a test
+This will create a F<lib/Hello/Controller/Site.pm> file (and a test
file). If you bring Site.pm up in your editor, you can see that
there's not much there to see.
-In C<lib/Hello/Controller/Site.pm>, add the following method:
+In F<lib/Hello/Controller/Site.pm>, add the following method:
sub test :Local {
my ( $self, $c ) = @_;
-
+
$c->stash(username => 'John',
template => 'site/test.tt');
}
$ mkdir root/site
-Create a new template file in that directory named C<root/site/test.tt>
+Create a new template file in that directory named F<root/site/test.tt>
and include a line like:
<p>Hello, [% username %]!</p>
You can jump to the next chapter of the tutorial here:
L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
-
-
=head1 AUTHORS
Gerda Shank, C<gerda.shank@gmail.com>
Copyright 2006-2011, Kennedy Clark, under the
Creative Commons Attribution Share-Alike License Version 3.0
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
+(L<https://creativecommons.org/licenses/by-sa/3.0/us/>).
=head1 CREATE A NEW APPLICATION
The remainder of the tutorial will build an application called C<MyApp>.
-First use the Catalyst C<catalyst.pl> script to initialize the framework
+First use the Catalyst F<catalyst.pl> script to initialize the framework
for the C<MyApp> application (make sure you aren't still inside the
directory of the C<Hello> application from the previous chapter of the
tutorial or in a directory that already has a "MyApp" subdirectory):
object (generally written as C<$c>) that Catalyst passes to every
component throughout the framework.
-Take a look at the file C<lib/MyApp.pm> that the helper created above.
+Take a look at the file F<lib/MyApp.pm> that the helper created above.
By default, Catalyst enables three plugins/flags:
=over 4
C<-Debug> Flag
Enables the Catalyst debug output you saw when we started the
-C<script/myapp_server.pl> development server earlier. You can remove
+F<script/myapp_server.pl> development server earlier. You can remove
this item when you place your application into production.
To be technically correct, it turns out that C<-Debug> is not a plugin,
Catalyst> line of your application class will be plugins, Catalyst
supports a limited number of flag options (of these, C<-Debug> is the
most common). See the documentation for
-L<https://metacpan.org/module/Catalyst|Catalyst.pm> to get details on
+L<Catalyst.pm|Catalyst> to get details on
other flags (currently C<-Engine>, C<-Home>, C<-Log>, and C<-Stats>).
If you prefer, there are several other ways to enable debug output:
=item *
-the C<$c-E<gt>debug> method on the C<$c> Catalyst context object
+the C<< $c->debug >> method on the C<$c> Catalyst context object
=item *
-the C<-d> option on the C<script/myapp_server.pl> script
+the C<-d> option on the F<script/myapp_server.pl> script
=item *
B<TIP>: Depending on your needs, it can be helpful to permanently remove
C<-Debug> from C<lib/MyApp.pm> and then use the C<-d> option to
-C<script/myapp_server.pl> to re-enable it when needed. We will not be
+F<script/myapp_server.pl> to re-enable it when needed. We will not be
using that approach in the tutorial, but feel free to make use of it in
your own projects.
C<ConfigLoader> provides an automatic way to load configurable
parameters for your application from a central
L<Config::General> file (versus having the values
-hard-coded inside your Perl modules). Config::General uses syntax very
+hard-coded inside your Perl modules). L<Config::General> uses syntax very
similar to Apache configuration files. We will see how to use this
feature of Catalyst during the authentication and authorization sections
(L<Chapter 5|Catalyst::Manual::Tutorial::05_Authentication> and
B<IMPORTANT NOTE:> If you are using a version of
L<Catalyst::Devel> prior to version 1.06, be aware that
Catalyst changed the default format from YAML to the more
-straightforward C<Config::General> style. This tutorial uses the newer
-C<myapp.conf> file for C<Config::General>. However, Catalyst supports
-both formats and will automatically use either C<myapp.conf> or
-C<myapp.yml> (or any other format supported by
+straightforward L<Config::General> style. This tutorial uses the newer
+C<myapp.conf> file for L<Config::General>. However, Catalyst supports
+both formats and will automatically use either F<myapp.conf> or
+F<myapp.yml> (or any other format supported by
L<Catalyst::Plugin::ConfigLoader> and
L<Config::Any>). If you are using a version of
-Catalyst::Devel prior to 1.06, you can convert to the newer format by
-simply creating the C<myapp.conf> file manually and deleting
-C<myapp.yml>. The default contents of the C<myapp.conf> you create
+L<Catalyst::Devel> prior to 1.06, you can convert to the newer format by
+simply creating the F<myapp.conf> file manually and deleting
+F<myapp.yml>. The default contents of the F<myapp.conf> you create
should only consist of one line:
name MyApp
=back
For our application, we want to add one new plugin to the mix. To do
-this, edit C<lib/MyApp.pm> (this file is generally referred to as your
+this, edit F<lib/MyApp.pm> (this file is generally referred to as your
I<application class>) and delete the lines with:
use Catalyst qw/
-Debug
ConfigLoader
Static::Simple
-
+
StackTrace
/;
-B<Note:> Recent versions of C<Catalyst::Devel> have used a variety of
+B<Note:> Recent versions of L<Catalyst::Devel> have used a variety of
techniques to load these plugins/flags. For example, you might see the
following:
=item *
C<__PACKAGE__> is just a shorthand way of referencing the name of the
-package where it is used. Therefore, in C<MyApp.pm>, C<__PACKAGE__> is
+package where it is used. Therefore, in F<MyApp.pm>, C<__PACKAGE__> is
equivalent to C<MyApp>.
=item *
=item *
If you want to see what the StackTrace error screen looks like, edit
-C<lib/MyApp/Controller/Root.pm> and put a C<die "Oops";> command in the
+F<lib/MyApp/Controller/Root.pm> and put a C<die "Oops";> command in the
C<sub index :Path :Args(0)> method. Then start the development server
and open C<http://localhost:3000/> in your browser. You should get a
screen that starts with "Caught exception in
created "/home/catalyst/MyApp/script/../lib/MyApp/Controller/Books.pm"
created "/home/catalyst/MyApp/script/../t/controller_Books.t"
-Then edit C<lib/MyApp/Controller/Books.pm> (as discussed in
+Then edit F<lib/MyApp/Controller/Books.pm> (as discussed in
L<Chapter 2|Catalyst::Manual::Tutorial::02_CatalystBasics> of
-the Tutorial, Catalyst has a separate directory under C<lib/MyApp> for
+the Tutorial, Catalyst has a separate directory under F<lib/MyApp> for
each of the three parts of MVC: C<Model>, C<View> and C<Controller>)
and add the following method to the controller:
=head2 list
-
+
Fetch all book objects and pass to books/list.tt2 in stash to be displayed
-
+
=cut
-
+
sub list :Local {
# Retrieve the usual Perl OO '$self' for this object. $c is the Catalyst
# 'Context' that's used to 'glue together' the various components
# that make up the application
my ($self, $c) = @_;
-
+
# Retrieve all of the book records as book model objects and store in the
# stash where they can be accessed by the TT template
# $c->stash(books => [$c->model('DB::Book')->all]);
# But, for now, use this code until we create the model later
$c->stash(books => '');
-
+
# Set the TT template to use. You will almost always want to do this
# in your action methods (action methods respond to user input in
# your controllers).
$c->stash(template => 'books/list.tt2');
}
-B<TIP>: See L<Appendix 1|Catalyst::Manual::Tutorial::10_Appendices> for
-tips on removing the leading spaces when cutting and pasting example
+B<TIP>: See L<Appendix 1|Catalyst::Manual::Tutorial::10_Appendices/APPENDIX 1: CUT AND PASTE FOR POD-BASED EXAMPLES>
+for tips on removing the leading spaces when cutting and pasting example
code from POD-based documents.
Programmers experienced with object-oriented Perl should recognize
B<:Path> -- C<:Path> actions let you map a method to an explicit URI
path. For example, "C<:Path('list')>" in
-C<lib/MyApp/Controller/Books.pm> would match on the URL
+F<lib/MyApp/Controller/Books.pm> would match on the URL
C<http://localhost:3000/books/list>, but "C<:Path('/list')>" would match
on C<http://localhost:3000/list> (because of the leading slash). You
can use C<:Args()> to specify how many arguments an action should
As mentioned in L<Chapter 2|Catalyst::Manual::Tutorial::02_CatalystBasics>
of the tutorial, views are where you render output, typically for
display in the user's web browser (but can generate other types of
-output such as PDF or JSON). The code in C<lib/MyApp/View> selects the
+output such as PDF or JSON). The code in F<lib/MyApp/View> selects the
I<type> of view to use, with the actual rendering template found in the
C<root> directory. As with virtually every aspect of Catalyst, options
abound when it comes to the specific view technology you adopt inside
Toolkit, known as TT (for more information on TT, see
L<http://www.template-toolkit.org>). Other somewhat popular view
technologies include Mason (L<http://www.masonhq.com> and
-L<http://www.masonbook.com>) and L<HTML::Template>
-(L<http://html-template.sourceforge.net>).
+L<https://masonbook.houseabsolute.com/book/>) and L<HTML::Template>.
=head2 Create a Catalyst View
controls the overall "look and feel" of your site from a single file or
set of files).
-Edit C<lib/MyApp/View/HTML.pm> and you should see something similar to
+Edit F<lib/MyApp/View/HTML.pm> and you should see something similar to
the following:
__PACKAGE__->config(
'.tt2'.
You can also configure components in your application class. For
-example, Edit C<lib/MyApp.pm> and you should see the default
-configuration above the call to C<_PACKAGE__-E<gt>setup> (your defaults
+example, Edit F<lib/MyApp.pm> and you should see the default
+configuration above the call to C<< _PACKAGE__->setup >> (your defaults
could be different depending on the version of Catalyst you are using):
__PACKAGE__->config(
Change this to match the following (insert a new
-C<__PACKAGE__-E<gt>config> below the existing statement):
+C<< __PACKAGE__->config >> below the existing statement):
__PACKAGE__->config(
name => 'MyApp',
);
This changes the base directory for your template files from C<root> to
-C<root/src>.
+F<root/src>.
Please stick with the settings above for the duration of the tutorial,
but feel free to use whatever options you desire in your applications
(as with most things in Perl, there's more than one way to do it...).
-B<Note:> We will use C<root/src> as the base directory for our template
+B<Note:> We will use F<root/src> as the base directory for our template
files, with a full naming convention of
-C<root/src/_controller_name_/_action_name_.tt2>. Another popular option
-is to use C<root/> as the base (with a full filename pattern of
-C<root/_controller_name_/_action_name_.tt2>).
+F<root/src/_controller_name_/_action_name_.tt2>. Another popular option
+is to use F<root/> as the base (with a full filename pattern of
+F<root/_controller_name_/_action_name_.tt2>).
=head2 Create a TT Template Page
$ mkdir -p root/src/books
-Then create C<root/src/books/list.tt2> in your editor and enter:
+Then create F<root/src/books/list.tt2> in your editor and enter:
[% # This is a TT comment. -%]
-
+
[%- # Provide a title -%]
[% META title = 'Book List' -%]
-
+
[% # Note That the '-' at the beginning or end of TT code -%]
[% # "chomps" the whitespace/newline at that end of the -%]
[% # output (use View Source in browser to see the effect) -%]
-
+
[% # Some basic HTML with a loop to display books -%]
<table>
<tr><th>Title</th><th>Rating</th><th>Author(s)</th></tr>
index values (see L<Template::Manual::Variables> for details and
examples). In addition to the usual L<Template::Toolkit> module Pod
documentation, you can access the TT manual at
-L<https://metacpan.org/module/Template::Manual>.
+L<Template::Manual>.
B<TIP:> While you can build all sorts of complex logic into your TT
templates, you should in general keep the "code" part of your templates
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 C<myapp01.sql> in
+(L<https://www.sqlite.org>), a popular database that is lightweight and
+easy to use. Be sure to get at least version 3. Open F<myapp01.sql> in
your editor and enter:
--
INSERT INTO book_author VALUES (4, 7);
INSERT INTO book_author VALUES (5, 8);
-Then use the following command to build a C<myapp.db> SQLite database:
+Then use the following command to build a F<myapp.db> SQLite database:
$ sqlite3 myapp.db < myapp01.sql
If you need to create the database more than once, you probably want to
issue the C<rm myapp.db> command to delete the database before you use
-the C<sqlite3 myapp.db E<lt> myapp01.sql> command.
+the C<< sqlite3 myapp.db < myapp01.sql >> command.
-Once the C<myapp.db> database file has been created and initialized, you
+Once the F<myapp.db> database file has been created and initialized, you
can use the SQLite command line environment to do a quick dump of the
database contents:
L<DBIx::Class::Schema::Loader::Base/naming> (version 0.05 or greater).
For using other databases, such as PostgreSQL or MySQL, see
-L<Appendix 2|Catalyst::Manual::Tutorial::10_Appendices>.
+L<Appendix 2|Catalyst::Manual::Tutorial::10_Appendices/APPENDIX 2: USING POSTGRESQL AND MYSQL>.
=head1 DATABASE ACCESS WITH DBIx::Class
=head2 Create Static DBIx::Class Schema Files
B<Note:> If you are not following along in the Tutorial Virtual Machine,
-please be sure that you have version 1.27 or higher of DBD::SQLite and
-version 0.39 or higher of Catalyst::Model::DBIC::Schema. (The Tutorial
+please be sure that you have version 1.27 or higher of L<DBD::SQLite> and
+version 0.39 or higher of L<Catalyst::Model::DBIC::Schema>. (The Tutorial
VM already has versions that are known to work.) You can get your
currently installed version numbers with the following commands.
$ perl -MCatalyst::Model::DBIC::Schema\ 999
$ perl -MDBD::SQLite\ 999
-Before you continue, make sure your C<myapp.db> database file is in the
+Before you continue, make sure your F<myapp.db> database file is in the
application's topmost directory. Now use the model helper with the
C<create=static> option to read the database with
L<DBIx::Class::Schema::Loader> and
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.
-The C<script/myapp_create.pl> command breaks down like this:
+The F<script/myapp_create.pl> command breaks down like this:
=over 4
=item *
C<DB> is the name of the model class to be created by the helper in
-the C<lib/MyApp/Model> directory.
+the F<lib/MyApp/Model> directory.
=item *
=item *
C<MyApp::Schema> is the name of the DBIC schema file written to
-C<lib/MyApp/Schema.pm>.
+F<lib/MyApp/Schema.pm>.
=item *
C<create=static> causes L<DBIx::Class::Schema::Loader> to load the
schema as it runs and then write that information out into
-C<lib/MyApp/Schema.pm> and files under the C<lib/MyApp/Schema>
+F<lib/MyApp/Schema.pm> and files under the F<lib/MyApp/Schema>
directory.
=item *
L<DBIx::Class::Schema::Loader> create
foreign key relationships for us (this is not needed for databases such
as PostgreSQL and MySQL, but is required for SQLite). If you take a look
-at C<lib/MyApp/Model/DB.pm>, you will see that the SQLite pragma is
+at F<lib/MyApp/Model/DB.pm>, you will see that the SQLite pragma is
propagated to the Model, so that SQLite's recent (and optional) foreign
key enforcement is enabled at the start of every database connection.
=back
-If you look in the C<lib/MyApp/Schema.pm> file, you will find that it
+If you look in the F<lib/MyApp/Schema.pm> file, you will find that it
only contains a call to the C<load_namespaces> method. You will also
-find that C<lib/MyApp> contains a C<Schema> subdirectory, which then has
+find that F<lib/MyApp> contains a C<Schema> subdirectory, which then has
a subdirectory called "Result". This "Result" subdirectory then has
files named according to each of the tables in our simple database
-(C<Author.pm>, C<BookAuthor.pm>, and C<Book.pm>). These three files are
+(F<Author.pm>, F<BookAuthor.pm>, and F<Book.pm>). These three files are
called "Result Classes" (or
"L<ResultSource Classes|DBIx::Class::ResultSource>") in DBIx::Class
nomenclature. Although the Result Class files are named after tables in
L<Catalyst::Manual::Tutorial::04_BasicCRUD/EXPLORING THE POWER OF DBIC>).
The idea with the Result Source files created under
-C<lib/MyApp/Schema/Result> by the C<create=static> option is to only
+F<lib/MyApp/Schema/Result> by the C<create=static> option is to only
edit the files below the C<# DO NOT MODIFY THIS OR ANYTHING ABOVE!>
warning. If you place all of your changes below that point in the file,
you can regenerate the automatically created information at the top of
Also note the "flow" of the model information across the various files
and directories. Catalyst will initially load the model from
-C<lib/MyApp/Model/DB.pm>. This file contains a reference to
-C<lib/MyApp/Schema.pm>, so that file is loaded next. Finally, the call
+F<lib/MyApp/Model/DB.pm>. This file contains a reference to
+F<lib/MyApp/Schema.pm>, so that file is loaded next. Finally, the call
to C<load_namespaces> in C<Schema.pm> will load each of the "Result
-Class" files from the C<lib/MyApp/Schema/Result> subdirectory. The
+Class" files from the F<lib/MyApp/Schema/Result> subdirectory. The
final outcome is that Catalyst will dynamically create three
table-specific Catalyst models every time the application starts (you
can see these three model files listed in the debug output generated
when you launch the application).
-Additionally, the C<lib/MyApp/Schema.pm> model can easily be loaded
+Additionally, the F<lib/MyApp/Schema.pm> model can easily be loaded
outside of Catalyst, for example, in command-line utilities and/or cron
-jobs. C<lib/MyApp/Model/DB.pm> provides a very thin "bridge" between
+jobs. F<lib/MyApp/Model/DB.pm> provides a very thin "bridge" between
Catalyst and this external database model. Once you see how we can
add some powerful features to our DBIC model in
L<Chapter 4|Catalyst::Manual::Tutorial::04_BasicCRUD>, the elegance
=head1 ENABLE THE MODEL IN THE CONTROLLER
-Open C<lib/MyApp/Controller/Books.pm> and un-comment the model code we
+Open F<lib/MyApp/Controller/Books.pm> and un-comment the model code we
left disabled earlier so that your version matches the following
-(un-comment the line containing C<[$c-E<gt>model('DB::Book')-E<gt>all]>
+(un-comment the line containing C<< [$c->model('DB::Book')->all] >>
and delete the next 2 lines):
=head2 list
-
+
Fetch all book objects and pass to books/list.tt2 in stash to be displayed
-
+
=cut
-
+
sub list :Local {
# Retrieve the usual Perl OO '$self' for this object. $c is the Catalyst
# 'Context' that's used to 'glue together' the various components
# that make up the application
my ($self, $c) = @_;
-
+
# Retrieve all of the book records as book model objects and store
# in the stash where they can be accessed by the TT template
$c->stash(books => [$c->model('DB::Book')->all]);
-
+
# Set the TT template to use. You will almost always want to do this
# in your action methods (action methods respond to user input in
# your controllers).
$c->stash(template => 'books/list.tt2');
}
-B<TIP>: You may see the C<$c-E<gt>model('DB::Book')> un-commented above
-written as C<$c-E<gt>model('DB')-E<gt>resultset('Book')>. The two are
-equivalent. Either way, C<$c-E<gt>model> returns a
+B<TIP>: You may see the C<< $c->model('DB::Book') >> un-commented above
+written as C<< $c->model('DB')->resultset('Book') >>. The two are
+equivalent. Either way, C<< $c->model >> returns a
L<DBIx::Class::ResultSet> which handles queries
against the database and iterating over the set of results that is
returned.
-We are using the C<-E<gt>all> to fetch all of the books. DBIC supports
+We are using the C<< ->all >> to fetch all of the books. DBIC supports
a wide variety of more advanced operations to easily do things like
filtering and sorting the results. For example, the following could be
used to sort the results by descending title:
C<setenv DBIC_TRACE 1>).
B<NOTE:> You can also set this in your code using
-C<$class-E<gt>storage-E<gt>debug(1);>. See
+C<< $class->storage->debug(1); >>. See
L<DBIx::Class::Manual::Troubleshooting> for details (including options
to log to a file instead of displaying to the Catalyst development
server log).
| Catalyst::Plugin::ConfigLoader 0.30 |
| Catalyst::Plugin::StackTrace 0.11 |
'----------------------------------------------------------------------------'
-
+
[debug] Loaded dispatcher "Catalyst::Dispatcher"
[debug] Loaded engine "Catalyst::Engine"
[debug] Found home "/home/catalyst/MyApp"
| MyApp::Model::DB::BookAuthor | class |
| MyApp::View::HTML | instance |
'-----------------------------------------------------------------+----------'
-
+
[debug] Loaded Private actions:
.----------------------+--------------------------------------+--------------.
| Private | Class | Method |
| /books/index | MyApp::Controller::Books | index |
| /books/list | MyApp::Controller::Books | list |
'----------------------+--------------------------------------+--------------'
-
+
[debug] Loaded Path actions:
.-------------------------------------+--------------------------------------.
| Path | Private |
| /books | /books/index |
| /books/list | /books/list |
'-------------------------------------+--------------------------------------'
-
+
[info] MyApp powered by Catalyst 5.80020
HTTP::Server::PSGI: Accepting connections at http://0:3000
-B<NOTE:> Be sure you run the C<script/myapp_server.pl> command from the
-'base' directory of your application, not inside the C<script> directory
-itself or it will not be able to locate the C<myapp.db> database file.
+B<NOTE:> Be sure you run the F<script/myapp_server.pl> command from the
+'base' directory of your application, not inside the F<script> directory
+itself or it will not be able to locate the F<myapp.db> database file.
You can use a fully qualified or a relative path to locate the database
file, but we did not specify that when we ran the model helper earlier.
Next, to view the book list, change the URL in your browser to
L<http://localhost:3000/books/list>. You should get a list of the five
-books loaded by the C<myapp01.sql> script above without any formatting.
+books loaded by the F<myapp01.sql> script above without any formatting.
The rating for each book should appear on each row, but the "Author(s)"
column will still be blank (we will fill that in later).
-Also notice in the output of the C<script/myapp_server.pl> that
+Also notice in the output of the F<script/myapp_server.pl> that
L<DBIx::Class> used the following SQL to retrieve the data:
SELECT me.id, me.title, me.rating FROM book me
In order to create a wrapper, you must first edit your TT view and tell
it where to find your wrapper file.
-Edit your TT view in C<lib/MyApp/View/HTML.pm> and change it to match
+Edit your TT view in F<lib/MyApp/View/HTML.pm> and change it to match
the following:
__PACKAGE__->config(
Next you need to set up your wrapper template. Basically, you'll want
to take the overall layout of your site and put it into this file. For
-the tutorial, open C<root/src/wrapper.tt2> and input the following:
+the tutorial, open F<root/src/wrapper.tt2> and input the following:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" [%#
<title>[% template.title or "My Catalyst App!" %]</title>
<link rel="stylesheet" href="[% c.uri_for('/static/css/main.css') %]" />
</head>
-
+
<body>
<div id="outer">
<div id="header">
[%# Insert the page title -%]
<h1>[% template.title or site.title %]</h1>
</div>
-
+
<div id="bodyblock">
<div id="menu">
Navigation:
%]" title="Catalyst Welcome Page">Welcome</a></li>
</ul>
</div><!-- end menu -->
-
+
<div id="content">
[%# Status and error messages %]
<span class="message">[% status_msg %]</span>
[% content %]
</div><!-- end content -->
</div><!-- end bodyblock -->
-
+
<div id="footer">Copyright (c) your name goes here</div>
</div><!-- end outer -->
-
+
</body>
</html>
<span class="error">[% error_msg %]</span>
If we set either message in the Catalyst stash (e.g.,
-C<$c-E<gt>stash-E<gt>{status_msg} = 'Request was successful!'>) it will
+C<< $c->stash->{status_msg} = 'Request was successful!' >>) it will
be displayed whenever any view used by that request is rendered. The
C<message> and C<error> CSS styles can be customized to suit your needs
-in the C<root/static/css/main.css> file we create below.
+in the F<root/static/css/main.css> file we create below.
B<Notes:>
=item *
Although it is beyond the scope of this tutorial, you may wish to use a
-JavaScript or AJAX tool such as jQuery (L<http://www.jquery.com>) or
-Dojo (L<http://www.dojotoolkit.org>).
+JavaScript or AJAX tool such as jQuery (L<https://www.jquery.com>) or
+Dojo (L<https://dojotoolkit.org/>).
=back
$ mkdir root/static/css
-Then open the file C<root/static/css/main.css> (the file referenced in
+Then open the file F<root/static/css/main.css> (the file referenced in
the stylesheet href link of our wrapper above) and add the following
content:
Hit "Reload" in your web browser and you should now see a formatted
version of our basic book list. (Again, the development server should
have automatically restarted when you made changes to
-C<lib/MyApp/View/HTML.pm>. If you are not using the "-r" option, you
+F<lib/MyApp/View/HTML.pm>. If you are not using the "-r" option, you
will need to hit C<Ctrl-C> and manually restart it. Also note that the
development server does I<NOT> need to restart for changes to the TT and
static files we created and edited in the C<root> directory -- those
If you take a look at the Schema files automatically generated by
L<DBIx::Class::Schema::Loader>, you will see that it has already defined
C<has_many> and C<belongs_to> relationships on each side of our foreign
-keys. For example, take a look at C<lib/MyApp/Schema/Result/Book.pm> and
+keys. For example, take a look at F<lib/MyApp/Schema/Result/Book.pm> and
notice the following code:
=head1 RELATIONS
-
+
=head2 book_authors
-
+
Type: has_many
-
+
Related object: L<MyApp::Schema::Result::BookAuthor>
-
+
=cut
-
+
__PACKAGE__->has_many(
"book_authors",
"MyApp::Schema::Result::BookAuthor",
C<belongs_to> relationships. We recommend upgrading to the versions
specified above. :-)
-Have a look at C<lib/MyApp/Schema/Result/BookAuthor.pm> and notice that
+Have a look at F<lib/MyApp/Schema/Result/BookAuthor.pm> and notice that
there is a C<belongs_to> relationship defined that acts as the "mirror
image" to the C<has_many> relationship we just looked at above:
=head1 RELATIONS
-
+
=head2 book
-
+
Type: belongs_to
-
+
Related object: L<MyApp::Schema::Result::Book>
-
+
=cut
-
+
__PACKAGE__->belongs_to(
"book",
"MyApp::Schema::Result::Book",
automatically handle the C<has_many> and C<belongs_to> relationships,
C<many_to_many> relationship bridges (not technically a relationship)
currently need to be manually inserted. To add a C<many_to_many>
-relationship bridge, first edit C<lib/MyApp/Schema/Result/Book.pm> and
+relationship bridge, first edit F<lib/MyApp/Schema/Result/Book.pm> and
add the following text below the C<# You can replace this text...>
comment:
The C<many_to_many> relationship bridge is optional, but it makes it
easier to map a book to its collection of authors. Without it, we would
have to "walk" through the C<book_author> table as in
-C<$book-E<gt>book_author-E<gt>first-E<gt>author-E<gt>last_name> (we will
+C<< $book->book_author->first->author->last_name >> (we will
see examples on how to use DBIx::Class objects in your code soon, but
-note that because C<$book-E<gt>book_author> can return multiple authors,
+note that because C<< $book->book_author >> can return multiple authors,
we have to use C<first> to display a single author). C<many_to_many>
allows us to use the shorter
-C<$book-E<gt>author-E<gt>first-E<gt>last_name>. Note that you cannot
+C<< $book->author->first->last_name >>. Note that you cannot
define a C<many_to_many> relationship bridge without also having the
C<has_many> relationship in place.
-Then edit C<lib/MyApp/Schema/Result/Author.pm> and add the reverse
+Then edit F<lib/MyApp/Schema/Result/Author.pm> and add the reverse
C<many_to_many> relationship bridge for C<Author> as follows (again, be
careful to put in above the C<1;> but below the C<# DO NOT MODIFY THIS
OR ANYTHING ABOVE!> comment):
Let's add a new column to our book list page that takes advantage of the
relationship information we manually added to our schema files in the
-previous section. Edit C<root/src/books/list.tt2> and replace the
-"empty" table cell "<td></td>" with the following:
+previous section. Edit F<root/src/books/list.tt2> and replace the
+"empty" table cell "C<< <td></td> >>" with the following:
...
<td>
DBIx::Class):
SELECT me.id, me.title, me.rating FROM book me:
- SELECT author.id, author.first_name, author.last_name FROM book_author me
+ 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 = ? ): '1'
- SELECT author.id, author.first_name, author.last_name FROM book_author me
+ 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 = ? ): '2'
- SELECT author.id, author.first_name, author.last_name FROM book_author me
+ 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 = ? ): '3'
- SELECT author.id, author.first_name, author.last_name FROM book_author me
+ 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 = ? ): '4'
- SELECT author.id, author.first_name, author.last_name FROM book_author me
+ 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 = ? ): '5'
-Also note in C<root/src/books/list.tt2> that we are using "| html", a
-type of TT filter, to escape characters such as E<lt> and E<gt> to <
+Also note in F<root/src/books/list.tt2> that we are using "| html", a
+type of TT filter, to escape characters such as < and > to <
and > and avoid various types of dangerous hacks against your
application. In a real application, you would probably want to put "|
html" at the end of every field where a user has control over the
In some situations, it can be useful to run your application and display
a page without using a browser. Catalyst lets you do this using the
-C<script/myapp_test.pl> script. Just supply the URL you wish to
+F<script/myapp_test.pl> script. Just supply the URL you wish to
display and it will run that request through the normal controller
dispatch logic and use the appropriate view to render the output
(obviously, complex pages may dump a lot of text to your terminal
response output. Catalyst uses
L<Catalyst::Action::RenderView> by default
to automatically perform this operation. If you look in
-C<lib/MyApp/Controller/Root.pm>, you should see the empty definition for
+F<lib/MyApp/Controller/Root.pm>, you should see the empty definition for
the C<sub end> method:
sub end : ActionClass('RenderView') {}
=item *
-C<Root.pm> is designed to hold application-wide logic.
+F<Root.pm> is designed to hold application-wide logic.
=item *
C<end> method that's appropriate. For example, if the controller for a
request has an C<end> method defined, it will be called. However, if
the controller does not define a controller-specific C<end> method, the
-"global" C<end> method in C<Root.pm> will be called.
+"global" C<end> method in F<Root.pm> will be called.
=item *
logic in C<RenderView>. However, you can easily extend the
C<RenderView> logic by adding your own code inside the empty method body
(C<{}>) created by the Catalyst Helpers when we first ran the
-C<catalyst.pl> to initialize our application. See
+F<catalyst.pl> to initialize our application. See
L<Catalyst::Action::RenderView> for more
detailed information on how to extend C<RenderView> in C<sub end>.
You should get a page with the following message at the top:
- Caught exception in MyApp::Controller::Root->end "Forced debug -
+ Caught exception in MyApp::Controller::Root->end "Forced debug -
Scrubbed output at /usr/share/perl5/Catalyst/Action/RenderView.pm line 46."
Along with a summary of your application's state at the end of the
same name as your controller action, allowing you to save the step of
manually specifying the template name in each action. For example, this
would allow us to remove the
-C<$c-E<gt>stash-E<gt>{template} = 'books/list.tt2';>
+C<< $c->stash->{template} = 'books/list.tt2'; >>
line of our C<list> action in the Books controller.
Open C<lib/MyApp/Controller/Books.pm> in your editor and comment out
this line to match the following (only the
-C<$c-E<gt>stash-E<gt>{template}> line has changed):
+C<< $c->stash->{template} >> line has changed):
=head2 list
-
+
Fetch all book objects and pass to books/list.tt2 in stash to be displayed
-
+
=cut
-
+
sub list :Local {
# Retrieve the usual Perl OO '$self' for this object. $c is the Catalyst
# 'Context' that's used to 'glue together' the various components
# that make up the application
my ($self, $c) = @_;
-
+
# Retrieve all of the book records as book model objects and store in the
# stash where they can be accessed by the TT template
$c->stash(books => [$c->model('DB::Book')->all]);
-
+
# Set the TT template to use. You will almost always want to do this
# in your action methods (actions methods respond to user input in
# your controllers).
URL as before.
B<NOTE:> If you use the default template technique, you
-will B<not> be able to use either the C<$c-E<gt>forward> or the
-C<$c-E<gt>detach> mechanisms (these are discussed in Chapter 2 and
+will B<not> be able to use either the C<< $c->forward >> or the
+C<< $c->detach >> mechanisms (these are discussed in Chapter 2 and
Chapter 9 of the Tutorial).
B<IMPORTANT:> Make sure that you do B<not> skip the following section
=head2 Return To A Manually Specified Template
-In order to be able to use C<$c-E<gt>forward> and C<$c-E<gt>detach>
+In order to be able to use C<< $c->forward >> and C<< $c->detach >>
later in the tutorial, you should remove the comment from the statement
-in C<sub list> in C<lib/MyApp/Controller/Books.pm>:
+in C<sub list> in F<lib/MyApp/Controller/Books.pm>:
$c->stash(template => 'books/list.tt2');
-Then delete the C<TEMPLATE_EXTENSION> line in C<lib/MyApp/View/HTML.pm>.
+Then delete the C<TEMPLATE_EXTENSION> line in F<lib/MyApp/View/HTML.pm>.
Check the L<http://localhost:3000/books/list> URL in your browser. It
should look the same manner as with earlier sections.
Copyright 2006-2011, Kennedy Clark, under the
Creative Commons Attribution Share-Alike License Version 3.0
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
+(L<https://creativecommons.org/licenses/by-sa/3.0/us/>).
=head2 Include a Create Action in the Books Controller
-Edit C<lib/MyApp/Controller/Books.pm> and enter the following method:
+Edit F<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 @_. The args are separated by the '/' char on the URL.
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('DB::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 and set template
$c->stash(book => $book,
template => 'books/create_done.tt2');
-
+
# Disable caching for this page
$c->response->header('Cache-Control' => 'no-cache');
}
Also note that we are explicitly setting a C<no-cache> "Cache-Control"
header to force browsers using the page to get a fresh copy every time.
You could even move this to a C<auto> method in
-C<lib/MyApp/Controller/Root.pm> and it would automatically get applied
+F<lib/MyApp/Controller/Root.pm> and it would automatically get applied
to every page in the whole application via a single line of code
(remember from Chapter 3, that every C<auto> method gets run in the
Controller hierarchy).
=head2 Include a Template for the 'url_create' Action:
-Edit C<root/src/books/create_done.tt2> and then enter:
+Edit F<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 updating -%]
[% # the title in the root/src/wrapper.tt2 wrapper template). Note that -%]
[% # interpolation -- if you need dynamic/interpolated content in your -%]
[% # title, set "$c->stash(title => $something)" in the controller). -%]
[% META title = 'Book Created' %]
-
+
[% # Output information about the record that was added. First title. -%]
<p>Added book '[% book.title %]'
-
+
[% # Then, output the last name of the first author -%]
by '[% book.authors.first.last_name %]'
-
+
[% # Then, output the rating for the book that was added -%]
with a rating of [% book.rating %].</p>
-
+
[% # Provide a link back to the list page. 'c.uri_for' builds -%]
[% # a full URI; e.g., 'http://localhost:3000/books/list' -%]
<p><a href="[% c.uri_for('/books/list') %]">Return to list</a></p>
-
+
[% # Try out the TT Dumper (for development only!) -%]
<pre>
Dump of the 'book' variable:
browser at the C</books/list> page). You should now 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'
method that we saw in the previous chapter of the tutorial, there is an
alternate approach that allows us to be more specific while also paving
the way for more advanced capabilities. Change the method declaration
-for C<url_create> in C<lib/MyApp/Controller/Books.pm> you entered above
+for C<url_create> in F<lib/MyApp/Controller/Books.pm> you entered above
to match the following:
sub url_create :Chained('/') :PathPart('books/url_create') :Args(3) {
# In addition to self & context, get the title, rating, &
# author_id args from the URL. Note that Catalyst automatically
- # puts the first 3 arguments worth of extra information after the
+ # puts the first 3 arguments worth of extra information after the
# "/<controller_name>/<action_name/" into @_ because we specified
# "Args(3)". The args are separated by the '/' char on the URL.
my ($self, $c, $title, $rating, $author_id) = @_;
-
+
...
This converts the method to take advantage of the Chained
=item *
-B<Do NOT get arguments through "C<CaptureArgs()>," use "C<Args()>" instead to end a chain>
+B<< Do NOT get arguments through "C<CaptureArgs()>," use "C<Args()>" instead to end a chain >>
=item *
| /books | /books/index |
| /books/list | /books/list |
'-------------------------------------+--------------------------------------'
-
+
[debug] Loaded Chained actions:
.-------------------------------------+--------------------------------------.
| Path Spec | Private |
Let's make a quick update to our initial Chained action to show a little
more of the power of chaining. First, open
-C<lib/MyApp/Controller/Books.pm> in your editor and add the following
+F<lib/MyApp/Controller/Books.pm> in your editor and add the following
method:
=head2 base
-
+
Can place common logic to start chained dispatch here
-
+
=cut
-
+
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::Book'));
-
+
# Print a message to the debug log
$c->log->debug('*** INSIDE BASE METHOD ***');
}
Here we print a log message and store the DBIC ResultSet in
-C<$c-E<gt>stash-E<gt>{resultset}> so that it's automatically available
+C<< $c->stash->{resultset} >> so that it's automatically available
for other actions that chain off C<base>. If your controller always
needs a book ID as its first argument, you could have the base method
capture that argument (with C<:CaptureArgs(1)>) and use it to pull the
-book object with C<-E<gt>find($id)> and leave it in the stash for later
+book object with C<< ->find($id) >> 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<url_create> we are
working with now), we will instead add that functionality to a common
C<object> action shortly.
As for C<url_create>, let's modify it to first dispatch to C<base>.
-Open up C<lib/MyApp/Controller/Books.pm> and edit the declaration for
+Open up F<lib/MyApp/Controller/Books.pm> and edit the declaration for
C<url_create> to match the following:
sub url_create :Chained('base') :PathPart('url_create') :Args(3) {
-Once you save C<lib/MyApp/Controller/Books.pm>, notice that the
+Once you save F<lib/MyApp/Controller/Books.pm>, notice that the
development server will restart and our "Loaded Chained actions" section
will changed slightly:
"create" actions more than once. Don't worry about it as long as the
number of books is appropriate for the number of times you added new
books... there should be the original five books added via
-C<myapp01.sql> plus one additional book for each time you ran one of the
+F<myapp01.sql> plus one additional book for each time you ran one of the
url_create variations above.)
=head2 Add Method to Display The Form
-Edit C<lib/MyApp/Controller/Books.pm> and add the following method:
+Edit F<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 :Chained('base') :PathPart('form_create') :Args(0) {
my ($self, $c) = @_;
-
+
# Set the TT template to use
$c->stash(template => 'books/form_create.tt2');
}
=head2 Add a Template for the Form
-Open C<root/src/books/form_create.tt2> in your editor and enter:
+Open F<root/src/books/form_create.tt2> in your editor and enter:
[% META title = 'Manual Form Book Create' -%]
-
+
<form method="post" action="[% c.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>
+ <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>
=head2 Add a Method to Process Form Values and Update Database
-Edit C<lib/MyApp/Controller/Books.pm> and add the following method to
+Edit F<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 :Chained('base') :PathPart('form_create_do') :Args(0) {
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('DB::Book')->create({
title => $title,
$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});
-
+
# Store new model object in stash and set template
$c->stash(book => $book,
template => 'books/create_done.tt2');
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 see the output of the same
-C<create_done.tt2> template seen in earlier examples. Finally, click
+F<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
=head2 Include a Delete Link in the List
-Edit C<root/src/books/list.tt2> and update it to match the following
+Edit F<root/src/books/list.tt2> and update it to match the following
(two sections have changed: 1) the additional '<th>Links</th>' table
header, and 2) the five lines for the Delete link near the bottom):
[% # This is a TT comment. -%]
-
+
[%- # Provide a title -%]
[% META title = 'Book List' -%]
-
+
[% # Note That the '-' at the beginning or end of TT code -%]
[% # "chomps" the whitespace/newline at that end of the -%]
[% # output (use View Source in browser to see the effect) -%]
-
+
[% # Some basic HTML with a loop to display books -%]
<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>
- [% # NOTE: See Chapter 4 for a better way to do this! -%]
- [% # 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 doesn't return -%]
- [% # a value, so nothing will be printed here. But, if you have something -%]
- [% # in TT that does return a value and you don't want it printed, you -%]
- [% # 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 -%]
- [% # Note the use of the TT filter "| html" to escape dangerous characters -%]
- ([% tt_authors.size | html %])
- [% # Use another TT vmethod to join & print the names & comma separators -%]
- [% tt_authors.join(', ') | html %]
- </td>
- <td>
- [% # Add a link to delete a book %]
- <a href="[%
- c.uri_for(c.controller.action_for('delete'), [book.id]) %]">Delete</a>
- </td>
- </tr>
+ <tr>
+ <td>[% book.title %]</td>
+ <td>[% book.rating %]</td>
+ <td>
+ [% # NOTE: See Chapter 4 for a better way to do this! -%]
+ [% # 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 doesn't return -%]
+ [% # a value, so nothing will be printed here. But, if you have something -%]
+ [% # in TT that does return a value and you don't want it printed, you -%]
+ [% # 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 -%]
+ [% # Note the use of the TT filter "| html" to escape dangerous characters -%]
+ ([% tt_authors.size | html %])
+ [% # Use another TT vmethod to join & print the names & comma separators -%]
+ [% tt_authors.join(', ') | html %]
+ </td>
+ <td>
+ [% # Add a link to delete a book %]
+ <a href="[%
+ c.uri_for(c.controller.action_for('delete'), [book.id]) %]">Delete</a>
+ </td>
+ </tr>
[% END -%]
</table>
request).
Also notice that we are using a more advanced form of C<uri_for> than we
-have seen before. Here we use C<$c-E<gt>controller-E<gt>action_for> to
+have seen before. Here we use C<< $c->controller->action_for >> to
automatically generate a URI appropriate for that action based on the
method we want to link to while inserting the C<book.id> value into the
appropriate place. Now, if you ever change C<:PathPart('delete')> in
=item *
If you are referring to a method in the current controller, you can use
-C<$self-E<gt>action_for('_method_name_')>.
+C<< $self->action_for('_method_name_') >>.
=item *
If you are referring to a method in a different controller, you need to
include that controller's name as an argument to C<controller()>, as in
-C<$c-E<gt>controller('_controller_name_')-E<gt>action_for('_method_name_')>.
+C<< $c->controller('_controller_name_')->action_for('_method_name_') >>.
=back
C<url_create> that don't operate on an existing book can chain directly
off base.
-To add the C<object> method, edit C<lib/MyApp/Controller/Books.pm> and
+To add the C<object> method, edit F<lib/MyApp/Controller/Books.pm> and
add the following code:
=head2 object
-
+
Fetch the specified book object based on the book ID and store
it in the stash
-
+
=cut
-
+
sub object :Chained('base') :PathPart('id') :CaptureArgs(1) {
# $id = primary key of book to delete
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};
-
+
# Print a message to the debug log
$c->log->debug("*** INSIDE OBJECT METHOD for obj id=$id ***");
}
Now, any other method that chains off C<object> will automatically have
-the appropriate book waiting for it in C<$c-E<gt>stash-E<gt>{object}>.
+the appropriate book waiting for it in C<< $c->stash->{object} >>.
=head2 Add a Delete Action to the Controller
-Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
+Open F<lib/MyApp/Controller/Books.pm> in your editor and add the
following method:
=head2 delete
-
+
Delete a book
-
+
=cut
-
+
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_author' entries
$c->stash->{object}->delete;
-
+
# 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');
}
trouble.
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
+C<< $c->forward('list')) >> or C<< $c->detach('list')) >> that perform a
server-side alteration in the flow of processing, a redirect is a
client-side mechanism that causes the browser 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>
+F<lib/MyApp/Controller/Books.pm> and edit the existing C<sub delete>
method to match:
=head2 delete
-
+
Delete a book
-
+
=cut
-
+
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_author' entries
$c->stash->{object}->delete;
-
+
# 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. Note the use
# of $self->action_for as earlier in this section (BasicCRUD)
$c->response->redirect($c->uri_for($self->action_for('list')));
L<Chapter 5|Catalyst::Manual::Tutorial::05_Authentication> of this
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>
+F<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 :Chained('object') :PathPart('delete') :Args(0) {
my ($self, $c) = @_;
-
+
# Use the book object saved by 'object' and delete it along
# with related 'book_author' 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($self->action_for('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/src/wrapper.tt2> to handle C<status_msg> as a
+need to update F<root/src/wrapper.tt2> to handle C<status_msg> as a
query parameter:
...
...
Although the sample above only shows the C<content> div, leave the rest
-of the file intact -- the only change we made to the C<wrapper.tt2> was
+of the file intact -- the only change we made to the F<wrapper.tt2> was
to add "C<|| c.request.params.status_msg>" to the
-C<E<lt>span class="message"E<gt>> line. Note that we definitely want
+C<< <span class="message"> >> line. Note that we definitely want
the "C<| html>" TT filter here since it would be easy for users to
modify the message on the URL and possibly inject harmful code into the
application if we left that off.
include the L<DBIx::Class::TimeStamp> in the C<load_components> line of
the Result Classes.
-If you open C<lib/MyApp/Schema/Result/Book.pm> in your editor you should
+If you open F<lib/MyApp/Schema/Result/Book.pm> in your editor you should
see that the C<created> and C<updated> fields are now included in the
call to C<add_columns()>. However, also notice that the C<many_to_many>
relationships we manually added below the "C<# DO NOT MODIFY...>" line
were automatically preserved.
-While we C<lib/MyApp/Schema/Result/Book.pm> open, let's update it with
+While we F<lib/MyApp/Schema/Result/Book.pm> open, let's update it with
some additional information to have DBIC automatically handle the
updating of these two fields for us. Insert the following code at the
bottom of the file (it B<must> be B<below> the "C<# DO NOT MODIFY...>"
B<Note> that adding the lines above will cause the development server to
automatically restart if you are running it with the "-r" option. In
other words, the development server is smart enough to restart not only
-for code under the C<MyApp/Controller/>, C<MyApp/Model/>, and
-C<MyApp/View/> directories, but also under other directions such as our
-"external DBIC model" in C<MyApp/Schema/>. However, also note that it's
+for code under the F<MyApp/Controller/>, F<MyApp/Model/>, and
+F<MyApp/View/> directories, but also under other directions such as our
+"external DBIC model" in F<MyApp/Schema/>. However, also note that it's
smart enough to B<not> restart when you edit your C<.tt2> files under
-C<root/>.
+F<root/>.
Then enter the following URL into your web browser:
Notice in the debug log that the SQL DBIC generated has changed to
incorporate the datetime logic:
- INSERT INTO book ( created, rating, title, updated ) VALUES ( ?, ?, ?, ? ):
+ INSERT INTO book ( created, rating, title, updated ) VALUES ( ?, ?, ?, ? ):
'2010-02-16 04:18:42', '5', 'TCPIP_Illustrated_Vol-2', '2010-02-16 04:18:42'
INSERT INTO book_author ( author_id, book_id ) VALUES ( ?, ? ): '4', '10'
=head2 Create a ResultSet Class
An often overlooked but extremely powerful features of DBIC is that it
-allows you to supply your own subclasses of C<DBIx::Class::ResultSet>.
+allows you to supply your own subclasses of L<DBIx::Class::ResultSet>.
This can be used to pull complex and unsightly "query code" out of your
controllers and encapsulate it in a method of your ResultSet Class.
These "canned queries" in your ResultSet Class can then be invoked via a
$ mkdir lib/MyApp/Schema/ResultSet
-Then open C<lib/MyApp/Schema/ResultSet/Book.pm> and enter the following:
+Then open F<lib/MyApp/Schema/ResultSet/Book.pm> and enter the following:
package MyApp::Schema::ResultSet::Book;
-
+
use strict;
use warnings;
use base 'DBIx::Class::ResultSet';
-
+
=head2 created_after
-
+
A predefined search for recently added books
-
+
=cut
-
+
sub created_after {
my ($self, $datetime) = @_;
-
+
my $date_str = $self->result_source->schema->storage
->datetime_parser->format_datetime($datetime);
-
+
return $self->search({
created => { '>' => $date_str }
});
}
-
+
1;
-Then add the following method to the C<lib/MyApp/Controller/Books.pm>:
+Then add the following method to the F<lib/MyApp/Controller/Books.pm>:
=head2 list_recent
-
+
List recently created books
-
+
=cut
-
+
sub list_recent :Chained('base') :PathPart('list_recent') :Args(1) {
my ($self, $c, $mins) = @_;
-
+
# Retrieve all of the book records as book model objects and store in the
# stash where they can be accessed by the TT template, but only
# retrieve books created within the last $min number of minutes
$c->stash(books => [$c->model('DB::Book')
->created_after(DateTime->now->subtract(minutes => $mins))]);
-
+
# Set the TT template to use. You will almost always want to do this
# in your action methods (action methods respond to user input in
# your controllers).
=head2 Chaining ResultSets
-One of the most helpful and powerful features in C<DBIx::Class> is that
+One of the most helpful and powerful features in L<DBIx::Class> is that
it allows you to "chain together" a series of queries (note that this
has nothing to do with the "Chained Dispatch" for Catalyst that we were
discussing earlier). Because each ResultSet method returns another
implemented in the previous section for our "canned search", we can
combine the two capabilities. For example, let's add an action to our
C<Books> controller that lists books that are both recent I<and> have
-"TCP" in the title. Open up C<lib/MyApp/Controller/Books.pm> and add
+"TCP" in the title. Open up F<lib/MyApp/Controller/Books.pm> and add
the following method:
=head2 list_recent_tcp
-
+
List recently created books
-
+
=cut
-
+
sub list_recent_tcp :Chained('base') :PathPart('list_recent_tcp') :Args(1) {
my ($self, $c, $mins) = @_;
-
+
# Retrieve all of the book records as book model objects and store in the
# stash where they can be accessed by the TT template, but only
# retrieve books created within the last $min number of minutes
->created_after(DateTime->now->subtract(minutes => $mins))
->search({title => {'like', '%TCP%'}})
]);
-
+
# Set the TT template to use. You will almost always want to do this
# in your action methods (action methods respond to user input in
# your controllers).
Take a look at the DBIC_TRACE output in the development server log for
the first URL and you should see something similar to the following:
- SELECT me.id, me.title, me.rating, me.created, me.updated FROM book me
+ SELECT me.id, me.title, me.rating, me.created, me.updated FROM book me
WHERE ( ( title LIKE ? AND created > ? ) ): '%TCP%', '2010-02-16 02:49:32'
However, let's not pollute our controller code with this raw "TCP" query
-- it would be cleaner to encapsulate that code in a method on our
-ResultSet Class. To do this, open C<lib/MyApp/Schema/ResultSet/Book.pm>
+ResultSet Class. To do this, open F<lib/MyApp/Schema/ResultSet/Book.pm>
and add the following method:
=head2 title_like
-
+
A predefined search for books with a 'LIKE' search in the string
-
+
=cut
-
+
sub title_like {
my ($self, $title_str) = @_;
-
+
return $self->search({
title => { 'like' => "%$title_str%" }
});
We defined the search string as C<$title_str> to make the method more
flexible. Now update the C<list_recent_tcp> method in
-C<lib/MyApp/Controller/Books.pm> to match the following (we have
-replaced the C<-E<gt>search> line with the C<-E<gt>title_like> line
+F<lib/MyApp/Controller/Books.pm> to match the following (we have
+replaced the C<< ->search >> line with the C<< ->title_like >> line
shown here -- the rest of the method should be the same):
=head2 list_recent_tcp
-
+
List recently created books
-
+
=cut
-
+
sub list_recent_tcp :Chained('base') :PathPart('list_recent_tcp') :Args(1) {
my ($self, $c, $mins) = @_;
-
+
# Retrieve all of the book records as book model objects and store in the
# stash where they can be accessed by the TT template, but only
# retrieve books created within the last $min number of minutes
->created_after(DateTime->now->subtract(minutes => $mins))
->title_like('TCP')
]);
-
+
# Set the TT template to use. You will almost always want to do this
# in your action methods (action methods respond to user input in
# your controllers).
Whereas the ResultSet construct is used in DBIC to correspond to an
entire query, the Result Class construct is used to represent a row.
Therefore, we can add row-specific "helper methods" to our Result
-Classes stored in C<lib/MyApp/Schema/Result/>. For example, open
-C<lib/MyApp/Schema/Result/Author.pm> and add the following method (as
+Classes stored in F<lib/MyApp/Schema/Result/>. For example, open
+F<lib/MyApp/Schema/Result/Author.pm> and add the following method (as
always, it must be above the closing "C<1;>"):
#
#
sub full_name {
my ($self) = @_;
-
+
return $self->first_name . ' ' . $self->last_name;
}
This will allow us to conveniently retrieve both the first and last name
-for an author in one shot. Now open C<root/src/books/list.tt2> and
+for an author in one shot. Now open F<root/src/books/list.tt2> and
change the definition of C<tt_authors> from this:
...
The previous section illustrated how we could use a Result Class method
to print the full names of the authors without adding any extra code to
our view, but it still left us with a fairly ugly mess (see
-C<root/src/books/list.tt2>):
+F<root/src/books/list.tt2>):
...
<td>
- [% # NOTE: See Chapter 4 for a better way to do this! -%]
- [% # 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 print -%]
- [% # 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.full_name) FOREACH author = book.authors %]
- [% # Now use a TT 'virtual method' to display the author count in parens -%]
- [% # Note the use of the TT filter "| html" to escape dangerous characters -%]
- ([% tt_authors.size | html %])
- [% # Use another TT vmethod to join & print the names & comma separators -%]
- [% tt_authors.join(', ') | html %]
+ [% # NOTE: See Chapter 4 for a better way to do this! -%]
+ [% # 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 print -%]
+ [% # 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.full_name) FOREACH author = book.authors %]
+ [% # Now use a TT 'virtual method' to display the author count in parens -%]
+ [% # Note the use of the TT filter "| html" to escape dangerous characters -%]
+ ([% tt_authors.size | html %])
+ [% # Use another TT vmethod to join & print the names & comma separators -%]
+ [% tt_authors.join(', ') | html %]
</td>
...
Let's combine some of the techniques used earlier in this section to
clean this up. First, let's add a method to our Book Result Class to
return the number of authors for a book. Open
-C<lib/MyApp/Schema/Result/Book.pm> and add the following method:
+F<lib/MyApp/Schema/Result/Book.pm> and add the following method:
=head2 author_count
-
+
Return the number of authors for the current book
-
+
=cut
-
+
sub author_count {
my ($self) = @_;
-
+
# Use the 'many_to_many' relationship to fetch all of the authors for the current
# and the 'count' method in DBIx::Class::ResultSet to get a SQL COUNT
return $self->authors->count;
}
Next, let's add a method to return a list of authors for a book to the
-same C<lib/MyApp/Schema/Result/Book.pm> file:
+same F<lib/MyApp/Schema/Result/Book.pm> file:
=head2 author_list
-
+
Return a comma-separated list of authors for the current book
-
+
=cut
-
+
sub author_list {
my ($self) = @_;
-
- # Loop through all authors for the current book, calling all the 'full_name'
+
+ # Loop through all authors for the current book, calling all the 'full_name'
# Result Class method for each
my @names;
foreach my $author ($self->authors) {
push(@names, $author->full_name);
}
-
+
return join(', ', @names);
}
This method loops through each author, using the C<full_name> Result
-Class method we added to C<lib/MyApp/Schema/Result/Author.pm> in the
+Class method we added to F<lib/MyApp/Schema/Result/Author.pm> in the
prior section.
Using these two methods, we can simplify our TT code. Open
-C<root/src/books/list.tt2> and update the "Author(s)" table cell to
+F<root/src/books/list.tt2> and update the "Author(s)" table cell to
match the following:
...
<td>
- [% # Print count and author list using Result Class methods -%]
- ([% book.author_count | html %]) [% book.author_list | html %]
+ [% # Print count and author list using Result Class methods -%]
+ ([% book.author_count | html %]) [% book.author_list | html %]
</td>
...
Copyright 2006-2011, Kennedy Clark, under the
Creative Commons Attribution Share-Alike License Version 3.0
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
+(L<https://creativecommons.org/licenses/by-sa/3.0/us/>).
Catalyst::Manual::Tutorial::05_Authentication - Catalyst Tutorial - Chapter 5: Authentication
-
=head1 OVERVIEW
This is B<Chapter 5 of 10> for the Catalyst tutorial.
are also instructions for downloading the code in
L<Catalyst::Manual::Tutorial::01_Intro>.
-
=head1 BASIC AUTHENTICATION
This section explores how to add authentication logic to a Catalyst
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, Chapter 6). Create a new SQL script file by
-opening C<myapp02.sql> in your editor and insert:
+opening F<myapp02.sql> in your editor and insert:
--
-- Add users and role tables, along with a many-to-many join table
--
PRAGMA foreign_keys = ON;
CREATE TABLE users (
- id INTEGER PRIMARY KEY,
- username TEXT,
- password TEXT,
- email_address TEXT,
- first_name TEXT,
- last_name TEXT,
- active INTEGER
+ id INTEGER PRIMARY KEY,
+ username TEXT,
+ password TEXT,
+ email_address TEXT,
+ first_name TEXT,
+ last_name TEXT,
+ active INTEGER
);
CREATE TABLE role (
- id INTEGER PRIMARY KEY,
- role TEXT
+ id INTEGER PRIMARY KEY,
+ role TEXT
);
CREATE TABLE user_role (
- user_id INTEGER REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
- role_id INTEGER REFERENCES role(id) ON DELETE CASCADE ON UPDATE CASCADE,
- PRIMARY KEY (user_id, role_id)
+ user_id INTEGER REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
+ role_id INTEGER REFERENCES role(id) ON DELETE CASCADE ON UPDATE CASCADE,
+ PRIMARY KEY (user_id, role_id)
);
--
-- Load up some initial test data
INSERT INTO user_role VALUES (2, 1);
INSERT INTO user_role VALUES (3, 1);
-Then load this into the C<myapp.db> database with the following command:
+Then load this into the F<myapp.db> database with the following command:
$ sqlite3 myapp.db < myapp02.sql
Author.pm BookAuthor.pm Book.pm Role.pm User.pm UserRole.pm
Notice how the helper has added three new table-specific Result Source
-files to the C<lib/MyApp/Schema/Result> directory. And, more
+files to the F<lib/MyApp/Schema/Result> 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
tables. However, as a convenience for mapping Users to their assigned
roles (see L<Chapter 6|Catalyst::Manual::Tutorial::06_Authorization>),
we will also manually add a C<many_to_many> relationship. Edit
-C<lib/MyApp/Schema/Result/User.pm> add the following information between
+F<lib/MyApp/Schema/Result/User.pm> add the following information between
the C<# DO NOT MODIFY THIS OR ANYTHING ABOVE!> comment and the closing
C<1;>:
C<many_to_many> in the Users to Roles direction.
Note that we do not need to make any change to the
-C<lib/MyApp/Schema.pm> schema file. It simply tells DBIC to load all of
+F<lib/MyApp/Schema.pm> schema file. It simply tells DBIC to load all of
the Result Class and ResultSet Class files it finds below the
-C<lib/MyApp/Schema> directory, so it will automatically pick up our new
+F<lib/MyApp/Schema> directory, so it will automatically pick up our new
table information.
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. Assuming that you
-are following along and using the "-r" option on C<myapp_server.pl>,
+are following along and using the "-r" option on F<myapp_server.pl>,
then the development server should automatically reload (if not, press
C<Ctrl-C> to break out of the server if it's running and then enter
-C<script/myapp_server.pl> to start it). Look for the three new model
+F<script/myapp_server.pl> to start it). Look for the three new model
objects in the startup debug output:
...
- .-------------------------------------------------------------------+----------.
+ .-------------------------------------------------------------------+----------.
| Class | Type |
+-------------------------------------------------------------------+----------+
| MyApp::Controller::Books | instance |
=head2 Include Authentication and Session Plugins
-Edit C<lib/MyApp.pm> and update it as follows (everything below
+Edit F<lib/MyApp.pm> and update it as follows (everything below
C<StackTrace> is new):
# Load plugins
-Debug
ConfigLoader
Static::Simple
-
+
StackTrace
-
+
Authentication
-
+
Session
Session::Store::File
Session::State::Cookie
sets a reasonable set of defaults for us. (Note: the C<SimpleDB> here
has nothing to do with the SimpleDB offered in Amazon's web services
offerings -- here we are only talking about a "simple" way to use your
-DB as an authentication backend.) Open C<lib/MyApp.pm> and place the
-following text above the call to C<__PACKAGE__-E<gt>setup();>:
+DB as an authentication backend.) Open F<lib/MyApp.pm> and place the
+following text above the call to C<< __PACKAGE__->setup(); >>:
# Configure SimpleDB Authentication
__PACKAGE__->config(
},
);
-We could have placed this configuration in C<myapp.conf>, but placing it
-in C<lib/MyApp.pm> is probably a better place since it's not likely
+We could have placed this configuration in F<myapp.conf>, but placing it
+in F<lib/MyApp.pm> is probably a better place since it's not likely
something that users of your application will want to change during
deployment (or you could use a mixture: leave C<class> and C<user_model>
-defined in C<lib/MyApp.pm> as we show above, but place C<password_type>
-in C<myapp.conf> to allow the type of password to be easily modified
+defined in F<lib/MyApp.pm> as we show above, but place C<password_type>
+in F<myapp.conf> to allow the type of password to be easily modified
during deployment). We will stick with putting all of the
-authentication-related configuration in C<lib/MyApp.pm> for the
-tutorial, but if you wish to use C<myapp.conf>, just convert to the
+authentication-related configuration in F<lib/MyApp.pm> for the
+tutorial, but if you wish to use F<myapp.conf>, just convert to the
following code:
<Plugin::Authentication>
</Plugin::Authentication>
B<TIP:> Here is a short script that will dump the contents of
-C<MyApp->config> to L<Config::General> format in C<myapp.conf>:
+C<< MyApp->config >> to L<Config::General> format in F<myapp.conf>:
$ CATALYST_DEBUG=0 perl -Ilib -e 'use MyApp; use Config::General;
Config::General->new->save_file("myapp.conf", MyApp->config);'
Remember, Catalyst is designed to be very flexible, and leaves such
matters up to you, the designer and programmer.
-Then open C<lib/MyApp/Controller/Login.pm>, and update the definition of
+Then open F<lib/MyApp/Controller/Login.pm>, and update the definition of
C<sub index> 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
$c->stash(error_msg => "Empty username or password.")
unless ($c->user_exists);
}
-
+
# If either of above don't work out, send to the login page
$c->stash(template => 'login.tt2');
}
on I<only> C</login>, not C</login/somethingelse>.
Next, update the corresponding method in
-C<lib/MyApp/Controller/Logout.pm> to match:
+F<lib/MyApp/Controller/Logout.pm> 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('/'));
}
=head2 Add a Login Form TT Template Page
-Create a login form by opening C<root/src/login.tt2> and inserting:
+Create a login form by opening F<root/src/login.tt2> and inserting:
[% META title = 'Login' %]
-
+
<!-- Login form -->
<form method="post" action="[% c.uri_for('/login') %]">
- <table>
- <tr>
- <td>Username:</td>
- <td><input type="text" name="username" size="40" /></td>
- </tr>
- <tr>
- <td>Password:</td>
- <td><input type="password" name="password" size="40" /></td>
- </tr>
- <tr>
- <td colspan="2"><input type="submit" name="submit" value="Submit" /></td>
- </tr>
- </table>
+ <table>
+ <tr>
+ <td>Username:</td>
+ <td><input type="text" name="username" size="40" /></td>
+ </tr>
+ <tr>
+ <td>Password:</td>
+ <td><input type="password" name="password" size="40" /></td>
+ </tr>
+ <tr>
+ <td colspan="2"><input type="submit" name="submit" value="Submit" /></td>
+ </tr>
+ </table>
</form>
mechanism -- a I<global> mechanism that prevents users who have not
passed authentication from reaching any pages except the login page.
This is generally done via an C<auto> action/method in
-C<lib/MyApp/Controller/Root.pm>.
+F<lib/MyApp/Controller/Root.pm>.
-Edit the existing C<lib/MyApp/Controller/Root.pm> class file and insert
+Edit the existing F<lib/MyApp/Controller/Root.pm> 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'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 unauthenticated users to reach any action in the Login
# controller. To lock it down to a single action, we could use:
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
# 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;
}
every C<auto> method from the application/root controller down to the
most specific controller will be called. By placing the authentication
enforcement code inside the C<auto> method of
-C<lib/MyApp/Controller/Root.pm> (or C<lib/MyApp.pm>), it will be called
+F<lib/MyApp/Controller/Root.pm> (or F<lib/MyApp.pm>), it will be called
for I<every> request that is received by the entire application.
Let's say you want to provide some information on the login page that
changes depending on whether the user has authenticated yet. To do
-this, open C<root/src/login.tt2> in your editor and add the following
+this, open F<root/src/login.tt2> in your editor and add the following
lines to the bottom of the file:
...
<p>
[%
- # This code illustrates how certain parts of the TT
- # template will only be shown to users who have logged in
+ # This code illustrates how certain parts of the TT
+ # template will only be shown to users who have logged in
%]
[% IF c.user_exists %]
Please Note: You are already logged in as '[% c.user.username %]'.
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"
- comments.
+ 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"
+ comments.
%]
</p>
Worse case, you might have to manually set the time on your development
box instead of using NTP.
-Open C<root/src/books/list.tt2> and add the following lines to the
+Open F<root/src/books/list.tt2> and add the following lines to the
bottom (below the closing </table> tag):
...
<p>
- <a href="[% c.uri_for('/login') %]">Login</a>
- <a href="[% c.uri_for(c.controller.action_for('form_create')) %]">Create</a>
+ <a href="[% c.uri_for('/login') %]">Login</a>
+ <a href="[% c.uri_for(c.controller.action_for('form_create')) %]">Create</a>
</p>
Reload your browser and you should now see a "Login" and "Create" links
database by using a salted SHA-1 hash. If you are concerned about
cleartext passwords between the browser and your application, consider
using SSL/TLS, made easy with modules such as
-L<Catalyst::Plugin:RequireSSL> and L<Catalyst::ActionRole::RequireSSL>.
+L<Catalyst::Plugin::RequireSSL> and L<Catalyst::ActionRole::RequireSSL>.
=head2 Re-Run the DBIC::Schema Model Helper to Include DBIx::Class::PassphraseColumn
If you then open one of the Result Classes, you will see that it
includes PassphraseColumn in the C<load_components> line. Take a look
-at C<lib/MyApp/Schema/Result/User.pm> since that's the main class where
+at F<lib/MyApp/Schema/Result/User.pm> since that's the main class where
we want to use hashed and salted passwords:
__PACKAGE__->load_components("InflateColumn::DateTime", "TimeStamp", "PassphraseColumn");
=head2 Modify the "password" Column to Use PassphraseColumn
-Open the file C<lib/MyApp/Schema/Result/User.pm> and enter the following
+Open the file F<lib/MyApp/Schema/Result/User.pm> and enter the following
text below the "# DO NOT MODIFY THIS OR ANYTHING ABOVE!" line but above
the closing "1;":
Next, let's create a quick script to load some hashed and salted
passwords into the C<password> column of our C<users> table. Open the
-file C<set_hashed_passwords.pl> in your editor and enter the following
+file F<set_hashed_passwords.pl> in your editor and enter the following
text:
#!/usr/bin/perl
-
+
use strict;
use warnings;
-
+
use MyApp::Schema;
-
+
my $schema = MyApp::Schema->connect('dbi:SQLite:myapp.db');
-
+
my @users = $schema->resultset('User')->all;
-
+
foreach my $user (@users) {
$user->password('mypass');
$user->update;
}
-PassphraseColumn lets us simply call C<$user->check_password($password)>
+PassphraseColumn lets us simply call C<< $user->check_password($password) >>
to see if the user has supplied the correct password, or, as we show
-above, call C<$user->update($new_password)> to update the hashed
+above, call C<< $user->update($new_password) >> to update the hashed
password stored for this user.
Then run the following command:
$ DBIC_TRACE=1 perl -Ilib set_hashed_passwords.pl
We had to use the C<-Ilib> argument to tell Perl to look under the
-C<lib> directory for our C<MyApp::Schema> model.
+F<lib> directory for our C<MyApp::Schema> model.
The DBIC_TRACE output should show that the update worked:
=head2 Enable Hashed and Salted Passwords
-Edit C<lib/MyApp.pm> and update the config() section for
+Edit F<lib/MyApp.pm> and update the config() section for
C<Plugin::Authentication> it to match the following text (the only
change is to the C<password_type> field):
Catalyst::Plugin::Authentication::Store::DBIx::Class to call the
C<check_password> method we enabled on our C<password> columns.
-
=head2 Try Out the Hashed Passwords
The development server should restart as soon as your save the
-C<lib/MyApp.pm> file in the previous section. You should now be able to
+F<lib/MyApp.pm> file in the previous section. You should now be able to
go to L<http://localhost:3000/books/list> and login as before. When
done, click the "logout" link on the login page (or point your browser
at L<http://localhost:3000/logout>).
L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD> chapter of the
tutorial to take advantage of C<flash>.
-First, open C<lib/MyApp/Controller/Books.pm> and modify C<sub delete> to
+First, open F<lib/MyApp/Controller/Books.pm> and modify C<sub delete> to
match the following (everything after the model search line of code has
changed):
=head2 delete
-
+
Delete a book
-
+
=cut
-
+
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($self->action_for('list')));
}
-Next, open C<root/src/wrapper.tt2> and update the TT code to pull from
+Next, open F<root/src/wrapper.tt2> and update the TT code to pull from
flash vs. the C<status_msg> query parameter:
...
...
Although the sample above only shows the C<content> div, leave the rest
-of the file intact -- the only change we made to replace "||
-c.request.params.status_msg" with "c.flash.status_msg" in the
-C<E<lt>span class="message"E<gt>> line.
+of the file intact -- the only change we made to replace "C<||
+c.request.params.status_msg>" with "C<c.flash.status_msg>" in the
+C<< <span class="message"> >> line.
=head2 Try Out Flash
information.
-=head2 Switch To Catalyst::Plugin::StatusMessages
+=head2 Switch To Catalyst::Plugin::StatusMessages
Although the query parameter technique we used in
L<Chapter 4|Catalyst::Manual::Tutorial::04_BasicCRUD> and the C<flash>
it's only displayed the first time). The use of C<StatusMessage>
or a similar mechanism is recommended for all Catalyst applications.
-To enable C<StatusMessage>, first edit C<lib/MyApp.pm> and add
+To enable C<StatusMessage>, first edit F<lib/MyApp.pm> and add
C<StatusMessage> to the list of plugins:
use Catalyst qw/
-Debug
ConfigLoader
Static::Simple
-
+
StackTrace
-
+
Authentication
-
+
Session
Session::Store::File
Session::State::Cookie
-
+
StatusMessage
/;
-Then edit C<lib/MyApp/Controller/Books.pm> and modify the C<delete>
+Then edit F<lib/MyApp/Controller/Books.pm> and modify the C<delete>
action to match the following:
sub delete :Chained('object') :PathPart('delete') :Args(0) {
my ($self, $c) = @_;
-
+
# Saved the PK id for status_msg below
my $id = $c->stash->{object}->id;
-
+
# 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
$c->response->redirect($c->uri_for($self->action_for('list'),
{mid => $c->set_status_msg("Deleted book $id")}));
message. The easiest way to do this is to take advantage of the chained
dispatch we implemented in
L<Chapter 4|Catalyst::Manual::Tutorial::04_BasicCRUD>. Edit
-C<lib/MyApp/Controller/Books.pm> again and update the C<base> action to
+F<lib/MyApp/Controller/Books.pm> again and update the C<base> action to
match:
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::Book'));
-
+
# Print a message to the debug log
$c->log->debug('*** INSIDE BASE METHOD ***');
-
+
# Load status messages
$c->load_status_msgs;
}
sub list :Chained('base') :PathPart('list') :Args(0) {
Finally, let's clean up the status/error message code in our wrapper
-template. Edit C<root/src/wrapper.tt2> and change the "content" div
+template. Edit F<root/src/wrapper.tt2> and change the "content" div
to match the following:
<div id="content">
token, it is ignored -- thereby keeping the state of our status/error
messages in sync with the users actions).
-
You can jump to the next chapter of the tutorial here:
L<Authorization|Catalyst::Manual::Tutorial::06_Authorization>
-
=head1 AUTHOR
Kennedy Clark, C<hkclark@gmail.com>
Copyright 2006-2011, Kennedy Clark, under the
Creative Commons Attribution Share-Alike License Version 3.0
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
+(L<https://creativecommons.org/licenses/by-sa/3.0/us/>).
-Debug
ConfigLoader
Static::Simple
-
+
StackTrace
-
+
Authentication
Authorization::Roles
-
+
Session
Session::Store::File
Session::State::Cookie
...
<p>Hello [% c.user.username %], you have the following roles:</p>
-
+
<ul>
[% # Dump list of roles -%]
[% FOR role = c.user.roles %]<li>[% role %]</li>[% END %]
</ul>
-
+
<p>
[% # Add some simple role-specific logic to template %]
[% # Use $c->check_user_roles() to check authz -%]
[% # Give normal users a link for 'logout' %]
<a href="[% c.uri_for('/logout') %]">User Logout</a>
[% END %]
-
+
[% # Can also use $c->user->check_roles() to check authz -%]
[% IF c.check_user_roles('admin') %]
[% # Give admin users a link for 'create' %]
updating C<url_create> to match the following code:
=head2 url_create
-
+
Create a book with the supplied title and rating,
with manual authorization
-
+
=cut
-
+
sub url_create :Chained('base') :PathPart('url_create') :Args(3) {
# 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) = @_;
-
+
# Check the user's roles
if ($c->check_user_roles('admin')) {
# Call create() on the book model object. Pass the table
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 and set template
$c->stash(book => $book,
template => 'books/create_done.tt2');
to add it below the "C<DO NOT MODIFY ...>" line):
=head2 delete_allowed_by
-
+
Can the specified user delete the current book?
-
+
=cut
-
+
sub delete_allowed_by {
my ($self, $user) = @_;
-
+
# Only allow delete if user has 'admin' role
return $user->has_role('admin');
}
the "C<DO NOT MODIFY ...>" line:
=head2 has_role
-
+
Check if a user has the specified role
-
+
=cut
-
+
use Perl6::Junction qw/any/;
sub has_role {
my ($self, $role) = @_;
-
+
# Does this user posses the required role?
return any(map { $_->role } $self->roles) eq $role;
}
-Let's also add C<Perl6::Junction> to the requirements listed in
+Let's also add L<Perl6::Junction> to the requirements listed in
Makefile.PL:
requires 'Perl6::Junction';
-B<Note:> Feel free to use C<grep> in lieu of C<Perl6::Junction::any> if
-you prefer. Also, please don't let the use of the C<Perl6::Junction>
+B<Note:> Feel free to use C<grep> in lieu of L<Perl6::Junction::any|Perl6::Junction/any()> if
+you prefer. Also, please don't let the use of the L<Perl6::Junction>
module above lead you to believe that Catalyst is somehow dependent on
Perl 6... we are simply using that module for its
L<easy-to-read|http://blogs.perl.org/users/marc_sebastian_jakobs/2009/11/my-favorite-module-of-the-month-perl6junction.html>
match the following code:
=head2 delete
-
+
Delete a book
-
+
=cut
-
+
sub delete :Chained('object') :PathPart('delete') :Args(0) {
my ($self, $c) = @_;
-
+
# Check permissions
$c->detach('/error_noperms')
unless $c->stash->{object}->delete_allowed_by($c->user->get_object);
-
+
# Saved the PK id for status_msg below
my $id = $c->stash->{object}->id;
-
+
# 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
$c->response->redirect($c->uri_for($self->action_for('list'),
{mid => $c->set_status_msg("Deleted book $id")}));
C<lib/MyApp/Controller/Root.pm> and add this method:
=head2 error_noperms
-
+
Permissions error screen
-
+
=cut
-
+
sub error_noperms :Chained('/') :PathPart('error_noperms') :Args(0) {
my ($self, $c) = @_;
-
+
$c->stash(template => 'error_noperms.tt2');
}
Copyright 2006-2011, Kennedy Clark, under the
Creative Commons Attribution Share-Alike License Version 3.0
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
+(L<https://creativecommons.org/licenses/by-sa/3.0/us/>).
=over 4
-=item *
+=item *
Fans of C<log> and C<print> statements embedded in the code.
-=item *
+=item *
Fans of interactive debuggers.
=head1 LOG STATEMENTS
-Folks in the former group can use Catalyst's C<$c-E<gt>log> facility.
+Folks in the former group can use Catalyst's C<< $c->log >> facility.
(See L<Catalyst::Log> for more detail.) For example, if you add the
following code to a controller action method:
$c->log->info("Starting the foreach loop here");
-
+
$c->log->debug("Value of \$id is: ".$id);
Then the Catalyst development server will display your message along
# 'Context' that's used to 'glue together' the various components
# that make up the application
my ($self, $c) = @_;
-
+
$DB::single=1;
-
+
# Retrieve all of the book records as book model objects and store in the
# stash where they can be accessed by the TT template
$c->stash->{books} = [$c->model('DB::Book')->all];
-
+
# Set the TT template to use. You will almost always want to do this
# in your action methods.
$c->stash->{template} = 'books/list.tt2';
This will start the interactive debugger and produce output similar to:
- $ perl -d script/myapp_server.pl
-
+ $ perl -d script/myapp_server.pl
+
Loading DB routines from perl5db.pl version 1.3
Editor support available.
-
+
Enter h or `h h' for help, or `man perldebug' for more help.
-
+
main::(script/myapp_server.pl:16): my $debug = 0;
-
- DB<1>
+
+ DB<1>
Press the C<c> key and hit C<Enter> to continue executing the Catalyst
development server under the debugger. Although execution speed will be
MyApp::Controller::Books::list(/home/catalyst/MyApp/script/../lib/MyApp/Controller/Books.pm:48):
48: $c->stash->{books} = [$c->model('DB::Book')->all];
-
+
DB<1>
You now have the full Perl debugger at your disposal. First use the
SELECT me.id, me.title, me.rating, me.created, me.updated FROM book me:
MyApp::Controller::Books::list(/home/catalyst/MyApp/script/../lib/MyApp/Controller/Books.pm:53):
53: $c->stash->{template} = 'books/list.tt2';
-
+
DB<1>
This takes you to the next line of code where the template name is set.
_calculate_score
_collapse_cond
<lines removed for brevity>
-
+
DB<2>
We can also play with the model directly:
=over 4
-=item *
+=item *
Check the version of an installed module:
or die qq(module \"\$m\" is not installed\\n); \
print \$m->VERSION'"
-=item *
+=item *
Check if a modules contains a given method:
Copyright 2006-2011, Kennedy Clark, under the
Creative Commons Attribution Share-Alike License Version 3.0
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
+(L<https://creativecommons.org/licenses/by-sa/3.0/us/>).
$ prove -wl t
There will be a lot of output because we have the C<-Debug> flag enabled
-in C<lib/MyApp.pm> (see the C<CATALYST_DEBUG=0> tip below for a quick
+in F<lib/MyApp.pm> (see the C<CATALYST_DEBUG=0> tip below for a quick
and easy way to reduce the clutter). Look for lines like this for
errors:
failures in the default tests. You can fix this by making the following
changes:
-1) Change the line in C<t/01app.t> that reads:
+1) Change the line in F<t/01app.t> that reads:
ok( request('/')->is_success, 'Request should succeed' );
ok( request('/login')->is_success, 'Request should succeed' );
-2) Change the line in C<t/controller_Logout.t> that reads:
+2) Change the line in F<t/controller_Logout.t> that reads:
ok( request('/logout')->is_success, 'Request should succeed' );
ok( request('/logout')->is_redirect, 'Request should succeed' );
-3) Change the line in C<t/controller_Books.t> that reads:
+3) Change the line in F<t/controller_Books.t> that reads:
ok( request('/books')->is_success, 'Request should succeed' );
ok( request('/books')->is_redirect, 'Request should succeed' );
-4) Add the following statement to the top of C<t/view_HTML.t>:
+4) Add the following statement to the top of F<t/view_HTML.t>:
use MyApp;
C<--lib> if you prefer) is used to set the location of the Catalyst
C<lib> directory. With this command, you will get all of the usual
development server debug output, something most people prefer to disable
-while running tests cases. Although you can edit the C<lib/MyApp.pm> to
+while running tests cases. Although you can edit the F<lib/MyApp.pm> to
comment out the C<-Debug> plugin, it's generally easier to simply set
the C<CATALYST_DEBUG=0> environment variable. For example:
$ CATALYST_DEBUG=0 prove -wl t
-During the C<t/02pod> and C<t/03podcoverage> tests, you might notice the
+During the F<t/02pod.t> and F<t/03podcoverage.t> tests, you might notice the
C<all skipped: set TEST_POD to enable this test> warning message. To
execute the Pod-related tests, add C<TEST_POD=1> to the C<prove>
command:
benefits of testing on a live system without the messiness of having to
use an actual web server, and a real person to do the clicking.
-To create a sample test case, open the C<t/live_app01.t> file in your
+To create a sample test case, open the F<t/live_app01.t> file in your
editor and enter the following:
#!/usr/bin/env perl
-
+
use strict;
use warnings;
use Test::More;
-
+
# Need to specify the name of your app as arg on next line
# Can also do:
# use Test::WWW::Mechanize::Catalyst "MyApp";
-
+
BEGIN { use_ok("Test::WWW::Mechanize::Catalyst" => "MyApp") }
-
+
# Create two 'user agents' to simulate two different users ('test01' & 'test02')
my $ua1 = Test::WWW::Mechanize::Catalyst->new;
my $ua2 = Test::WWW::Mechanize::Catalyst->new;
-
+
# Use a simplified for loop to do tests that are common to both users
# Use get_ok() to make sure we can hit the base URL
# Second arg = optional description of test (will be displayed for failed tests)
# Use content_contains() to match on text in the html body
$_->content_contains("You need to log in to use this application",
"Check we are NOT logged in") for $ua1, $ua2;
-
+
# Log in as each user
# Specify username and password on the URL
$ua1->get_ok("http://localhost/login?username=test01&password=mypass", "Login 'test01'");
username => 'test02',
password => 'mypass',
});
-
+
# Go back to the login page and it should show that we are already logged in
$_->get_ok("http://localhost/login", "Return to '/login'") for $ua1, $ua2;
$_->title_is("Login", "Check for login page") for $ua1, $ua2;
$_->content_contains("Please Note: You are already logged in as ",
"Check we ARE logged in" ) for $ua1, $ua2;
-
+
# 'Click' the 'Logout' link (see also 'text_regex' and 'url_regex' options)
$_->follow_link_ok({n => 4}, "Logout via first link on page") for $ua1, $ua2;
$_->title_is("Login", "Check for login title") for $ua1, $ua2;
$_->content_contains("You need to log in to use this application",
"Check we are NOT logged in") for $ua1, $ua2;
-
+
# Log back in
$ua1->get_ok("http://localhost/login?username=test01&password=mypass",
"Login 'test01'");
"Login 'test02'");
# Should be at the Book List page... do some checks to confirm
$_->title_is("Book List", "Check for book list title") for $ua1, $ua2;
-
+
$ua1->get_ok("http://localhost/books/list", "'test01' book list");
$ua1->get_ok("http://localhost/login", "Login Page");
$ua1->get_ok("http://localhost/books/list", "'test01' book list");
-
+
$_->content_contains("Book List", "Check for book list title") for $ua1, $ua2;
# Make sure the appropriate logout buttons are displayed
$_->content_contains("/logout\">User Logout</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'");
-
+
# User 'test01' should be able to create a book with the "formless create" URL
$ua1->get_ok("http://localhost/books/url_create/TestTitle/2/4",
"'test01' formless create");
# Try a regular expression to combine the previous 3 checks & account for whitespace
$ua1->content_like(qr/Added book 'TestTitle'\s+by 'Stevens'\s+with a rating of 2./,
"Regex check");
-
+
# Make sure the new book shows in the list
$ua1->get_ok("http://localhost/books/list", "'test01' book list");
$ua1->title_is("Book List", "Check logged in and at book list");
$ua1->content_contains("Book List", "Book List page test");
$ua1->content_contains("TestTitle", "Look for 'TestTitle'");
-
+
# Make sure the new book can be deleted
# Get all the Delete links on the list page
my @delLinks = $ua1->find_all_links(text => 'Delete');
# Check that delete worked
$ua1->content_contains("Book List", "Book List page test");
$ua1->content_like(qr/Deleted book \d+/, "Deleted book #");
-
+
# User 'test02' should not be able to add a book
$ua2->get_ok("http://localhost/books/url_create/TestTitle2/2/5", "'test02' add");
$ua2->content_contains("Unauthorized!", "Check 'test02' cannot add");
-
+
done_testing;
-The C<live_app.t> test cases uses copious comments to explain each step
+The F<live_app.t> test cases uses copious comments to explain each step
of the process. In addition to the techniques shown here, there are a
variety of other methods available in L<Test::WWW::Mechanize::Catalyst>
(for example, regex-based matching). Consult
One solution is to allow the database specification to be overridden
with an environment variable. For example, open
-C<lib/MyApp/Model/DB.pm> in your editor and change the
-C<__PACKAGE__-E<gt>config(...> declaration to resemble:
+F<lib/MyApp/Model/DB.pm> in your editor and change the
+C<< __PACKAGE__->config(... >> declaration to resemble:
my $dsn = $ENV{MYAPP_DSN} ||= 'dbi:SQLite:myapp.db';
__PACKAGE__->config(
schema_class => 'MyApp::Schema',
-
+
connect_info => {
dsn => $dsn,
user => '',
Setting C<$ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }> to 'testing' in your test
script results in loading of an additional config file named
-C<myapp_testing.conf> after C<myapp.conf> which will override any
-parameters in C<myapp.conf>.
+F<myapp_testing.conf> after F<myapp.conf> which will override any
+parameters in F<myapp.conf>.
You should set the environment variable in the BEGIN block of your test
script to make sure it's set before your Catalyst application is
started.
The following is an example for a config and test script for a
-DBIx::Class model named MyDB and a controller named Foo:
+L<DBIx::Class> model named MyDB and a controller named Foo:
myapp_testing.conf:
use strict;
use warnings;
use Test::More;
-
+
BEGIN {
$ENV{ MYAPP_CONFIG_LOCAL_SUFFIX } = 'testing';
}
-
+
eval "use Test::WWW::Mechanize::Catalyst 'MyApp'";
plan $@
? ( skip_all => 'Test::WWW::Mechanize::Catalyst required' )
: ( tests => 2 );
-
+
ok( my $mech = Test::WWW::Mechanize::Catalyst->new, 'Created mech object' );
-
+
$mech->get_ok( 'http://localhost/foo' );
Copyright 2006-2011, Kennedy Clark, under the
Creative Commons Attribution Share-Alike License Version 3.0
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
+(L<https://creativecommons.org/licenses/by-sa/3.0/us/>).
Copyright 2006-2011, Kennedy Clark, under the
Creative Commons Attribution Share-Alike License Version 3.0
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
+(L<https://creativecommons.org/licenses/by-sa/3.0/us/>).
Catalyst::Manual::Tutorial::09_AdvancedCRUD::09_FormBuilder - Catalyst Tutorial - Chapter 9: Advanced CRUD - FormBuilder
-NOTE: This chapter of the tutorial is in progress. Feel free to
+NOTE: This chapter of the tutorial is in progress. Feel free to
volunteer to help out. :-)
=head1 OVERVIEW
=head2 Inherit From Catalyst::Controller::HTML::FormFu
-First, change your C<lib/MyApp/Controller/Books.pm> to inherit from
+First, change your F<lib/MyApp/Controller/Books.pm> to inherit from
L<Catalyst::Controller::HTML::FormFu> by changing the C<extends> line
from the default of:
=head2 Add Action to Display and Save the Form
-Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
+Open F<lib/MyApp/Controller/Books.pm> in your editor and add the
following method:
=head2 formfu_create
-
+
Use HTML::FormFu to create a new book
-
+
=cut
-
+
sub formfu_create :Chained('base') :PathPart('formfu_create') :Args(0) :FormConfig {
my ($self, $c) = @_;
-
+
# Get the form that the :FormConfig attribute saved in the stash
my $form = $c->stash->{form};
-
+
# Check if the form has been submitted (vs. displaying the initial
# form) and if the data passed validation. "submitted_and_valid"
# is shorthand for "$form->submitted && !$form->has_errors"
# Add the authors to it
$select->options(\@authors);
}
-
+
# Set the template
$c->stash(template => 'books/formfu_create.tt2');
}
=head2 Create a Form Config File
-Although C<HTML::FormFu> supports any configuration file handled by
+Although L<HTML::FormFu> supports any configuration file handled by
L<Config::Any>, most people tend to use YAML. First create a directory
to hold your form configuration files:
$ mkdir -p root/forms/books
-Then create the file C<root/forms/books/formfu_create.yml> and enter the
+Then create the file F<root/forms/books/formfu_create.yml> and enter the
following text:
---
# This is an optional 'mouse over' title pop-up
attributes:
title: Enter a book title here
-
+
# Another text field for the numeric rating
- type: Text
name: rating
label: Rating
attributes:
title: Enter a rating between 1 and 5 here
-
+
# Add a drop-down list for the author selection. Note that we will
# dynamically fill in all the authors from the controller but we
# could manually set items in the drop-list by adding this YAML code:
- type: Select
name: authors
label: Author
-
+
# The submit button
- type: Submit
name: submit
value: Submit
B<NOTE:> Copying and pasting YAML from Perl documentation is sometimes
-tricky. See the L<Config::General Config for this tutorial> section of
+tricky. See the L</Config::General Config for this tutorial> section of
this document for a more foolproof config format.
=head2 Update the CSS
-Edit C<root/static/css/main.css> and add the following lines to the
+Edit F<root/static/css/main.css> and add the following lines to the
bottom of the file:
...
=head2 Create a Template Page To Display The Form
-Open C<root/src/books/formfu_create.tt2> in your editor and enter the
+Open F<root/src/books/formfu_create.tt2> in your editor and enter the
following:
[% META title = 'Create/Update Book' %]
-
+
[%# Render the HTML::FormFu Form %]
[% form %]
-
- <p><a href="[% c.uri_for(c.controller.action_for('list'))
+
+ <p><a href="[% c.uri_for(c.controller.action_for('list'))
%]">Return to book list</a></p>
=head2 Add Links for Create and Update via C<HTML::FormFu>
-Open C<root/src/books/list.tt2> in your editor and add the following to
+Open F<root/src/books/list.tt2> in your editor and add the following to
the bottom of the existing file:
...
</p>
This adds a new link to the bottom of the book list page that we can use
-to easily launch our HTML::FormFu-based form.
+to easily launch our L<HTML::FormFu>-based form.
=head2 Test The HTML::FormFu Create Form
$ script/myapp_server.pl -r
Login as C<test01> (password: mypass). Once at the Book List page,
-click the new HTML::FormFu "Create" link at the bottom to display the
+click the new L<HTML::FormFu> "Create" link at the bottom to display the
form. Fill in the following values:
Title: Internetworking with TCP/IP Vol. II
=head2 Add Constraints
-Open C<root/forms/books/formfu_create.yml> in your editor and update it
+Open F<root/forms/books/formfu_create.yml> in your editor and update it
to match:
---
max: 40
# Override the default of 'Invalid input'
message: Length must be between 5 and 40 characters
-
+
# Another text field for the numeric rating
- type: Text
name: rating
min: 1
max: 5
message: "Must be between 1 and 5."
-
+
# Add a select list for the author selection. Note that we will
# dynamically fill in all the authors from the controller but we
# could manually set items in the select by adding this YAML code:
constraints:
# Make sure it's a number
- Integer
-
+
# The submit button
- type: Submit
name: submit
value: Submit
-
+
# Global filters and constraints.
constraints:
# The user cannot leave any fields blank
- HTMLEscape
B<NOTE:> Copying and pasting YAML from Perl documentation is sometimes
-tricky. See the L<Config::General Config for this tutorial> section of
+tricky. See the L</Config::General Config for this tutorial> section of
this document for a more foolproof config format.
The main changes are:
The C<Select> element for C<authors> is changed from a single-select
drop-down to a multi-select list by adding configuration for the
-C<multiple> and C<size> options in C<formfu_create.yml>.
+C<multiple> and C<size> options in F<formfu_create.yml>.
=item *
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.
-When you click Submit, the HTML::FormFu C<constraint> items will
+When you click Submit, the L<HTML::FormFu> C<constraint> items will
validate the logic and insert feedback as appropriate. Try adding blank
spaces at the front or the back of the title and note that it will be
removed.
=head1 CREATE AND UPDATE/EDIT ACTION
Let's expand the work done above to add an edit action. First, open
-C<lib/MyApp/Controller/Books.pm> and add the following method to the
+F<lib/MyApp/Controller/Books.pm> and add the following method to the
bottom:
=head2 formfu_edit
-
+
Use HTML::FormFu to update an existing book
-
+
=cut
-
- sub formfu_edit :Chained('object') :PathPart('formfu_edit') :Args(0)
+
+ sub formfu_edit :Chained('object') :PathPart('formfu_edit') :Args(0)
:FormConfig('books/formfu_create.yml') {
my ($self, $c) = @_;
-
+
# Get the specified book already saved by the 'object' method
my $book = $c->stash->{object};
-
+
# Make sure we were able to get a book
unless ($book) {
# Set an error message for the user & return to books list
{mid => $c->set_error_msg("Invalid book -- Cannot edit")}));
$c->detach;
}
-
+
# Get the form that the :FormConfig attribute saved in the stash
my $form = $c->stash->{form};
-
+
# Check if the form has been submitted (vs. displaying the initial
# form) and if the data passed validation. "submitted_and_valid"
# is shorthand for "$form->submitted && !$form->has_errors"
# Populate the form with existing values from DB
$form->model->default_values($book);
}
-
+
# Set the template
$c->stash(template => 'books/formfu_create.tt2');
}
We have to manually specify the name of the FormFu .yml file as an
argument to C<:FormConfig> because the name can no longer be
automatically deduced from the name of our action/method (by default,
-FormFu would look for a file named C<books/formfu_edit.yml>).
+FormFu would look for a file named F<books/formfu_edit.yml>).
=item *
=item *
If the form has been submitted and passes validation, we skip creating a
-new book and just use C<$form-E<gt>model-E<gt>update> to update the
+new book and just use C<< $form->model->update >> to update the
existing book.
=item *
If the form is being displayed for the first time (or has failed
validation and it being redisplayed), we use
-C<$form-E<gt>model-E<gt>default_values> to populate the form with data
+C<< $form->model->default_values >> to populate the form with data
from the database.
=back
-Then, edit C<root/src/books/list.tt2> and add a new link below the
+Then, edit F<root/src/books/list.tt2> and add a new link below the
existing "Delete" link that allows us to edit/update each existing book.
-The last E<lt>tdE<gt> cell in the book list table should look like the
+The last <td> cell in the book list table should look like the
following:
...
=head2 Config::General Config for this tutorial
If you are having difficulty with YAML config above, please save the
-below into the file C<formfu_create.conf> and delete the
-C<formfu_create.yml> file. The below is in L<Config::General> format
+below into the file F<formfu_create.conf> and delete the
+F<formfu_create.yml> file. The below is in L<Config::General> format
which follows the syntax of Apache config files.
- constraints Required
- <elements>
- <constraints>
- min 5
- max 40
- type Length
- message Length must be between 5 and 40 characters
- </constraints>
- filter TrimEdges
- filter HTMLEscape
- name title
- type Text
- label Title
- <attributes>
- title Enter a book title here
- </attributes>
- </elements>
- <elements>
- constraints Integer
- filter TrimEdges
- filter NonNumeric
- name rating
- type Text
- label Rating
- <attributes>
- title Enter a rating between 1 and 5 here
- </attributes>
- </elements>
- <elements>
- constraints Integer
- filter TrimEdges
- filter HTMLEscape
- name authors
- type Select
- label Author
- multiple 1
- size 3
- </elements>
- <elements>
- value Submit
- name submit
- type Submit
- </elements>
- indicator submit
+ constraints Required
+ <elements>
+ <constraints>
+ min 5
+ max 40
+ type Length
+ message Length must be between 5 and 40 characters
+ </constraints>
+ filter TrimEdges
+ filter HTMLEscape
+ name title
+ type Text
+ label Title
+ <attributes>
+ title Enter a book title here
+ </attributes>
+ </elements>
+ <elements>
+ constraints Integer
+ filter TrimEdges
+ filter NonNumeric
+ name rating
+ type Text
+ label Rating
+ <attributes>
+ title Enter a rating between 1 and 5 here
+ </attributes>
+ </elements>
+ <elements>
+ constraints Integer
+ filter TrimEdges
+ filter HTMLEscape
+ name authors
+ type Select
+ label Author
+ multiple 1
+ size 3
+ </elements>
+ <elements>
+ value Submit
+ name submit
+ type Submit
+ </elements>
+ indicator submit
=head1 AUTHOR
Copyright 2006-2011, Kennedy Clark, under the
Creative Commons Attribution Share-Alike License Version 3.0
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
+(L<https://creativecommons.org/licenses/by-sa/3.0/us/>).
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.
to or from the database. This was written using HTML::FormHandler version
0.28001.
-See
+See
L<Catalyst::Manual::Tutorial::09_AdvancedCRUD>
-for additional form management options other than
+for additional form management options other than
L<HTML::FormHandler>.
Use the following command to install L<HTML::FormHandler::Model::DBIC> directly
from CPAN:
- sudo cpan HTML::FormHandler::Model::DBIC
+ sudo cpan HTML::FormHandler::Model::DBIC
-It will install L<HTML::FormHandler> as a prerequisite.
+It will install L<HTML::FormHandler> as a prerequisite.
Also, add:
requires 'HTML::FormHandler::Model::DBIC';
-to your C<Makefile.PL>.
+to your F<Makefile.PL>.
=head1 HTML::FormHandler FORM CREATION
-This section looks at how L<HTML::FormHandler> can be used to
+This section looks at how L<HTML::FormHandler> can be used to
add additional functionality to the manually created form from Chapter 4.
-=head2 Using FormHandler in your controllers
+=head2 Using FormHandler in your controllers
FormHandler doesn'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>:
+Create the directory F<lib/MyApp/Form>. Create F<lib/MyApp/Form/Book.pm>:
package MyApp::Form::Book;
=head2 Add Action to Display and Save the Form
-At the top of the C<lib/MyApp/Controller/Books.pm> add:
+At the top of the F<lib/MyApp/Controller/Books.pm> add:
use MyApp::Form::Book;
=head2 Create a Template Page To Display The Form
-Open C<root/src/books/form.tt2> in your editor and enter the following:
+Open F<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
+Open F<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>
+ 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
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.
+integers.
=head2 Add Constraints
-Open C<lib/MyApp/Form/Book.pm> in your editor.
+Open F<lib/MyApp/Form/Book.pm> in your editor.
Restrict the title size and make it required:
=head2 Try Out the Updated Form
-Press C<Ctrl-C> to kill the previous server instance (if it's still
+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.
+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:
+Edit F<lib/MyApp/Controller/Books.pm> and add the following method:
+
-
=head2 edit
Edit an existing book with FormHandler
return $self->form($c, $c->stash->{object});
}
-Update the C<root/src/books/list.tt2>, adding an 'edit' link below the
+Update the F<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>
+ [% # 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
+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
+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
mailing list: http://groups.google.com/group/formhandler
- code: http://github.com/gshank/html-formhandler/tree/master
+ code: http://github.com/gshank/html-formhandler/tree/master
=head1 AUTHOR
Catalyst::Manual::Tutorial::10_Appendices - Catalyst Tutorial - Chapter 10: Appendices
-
=head1 OVERVIEW
This is B<Chapter 10 of 10> for the Catalyst tutorial.
=item *
-":0,$s/^ "
+C<":0,$s/^ ">
Removes four leading spaces from the entire file (from the first line,
C<0>, to the last line, C<$>).
=item *
-"%s/^ "
+C<"%s/^ ">
A shortcut for the previous item (C<%> specifies the entire file; so
this removes four leading spaces from every line).
=item *
-":.,$s/^ "
+C<":.,$s/^ ">
Removes the first four spaces from the line the cursor is on at the time
the regex command is executed (".") to the last line of the file.
=item *
-":.,44s/^ "
+C<":.,44s/^ ">
Removes four leading space from the current line through line 44
(obviously adjust the C<44> to the appropriate value in your example).
You can limit the replacement operation by selecting text first (depending
on your version of Emacs, you can either use the mouse or experiment with
commands such as C<C-SPC> to set the mark at the cursor location and
-C<C-E<lt>> and C<C-E<gt>> to set the mark at the beginning and end of the
+C<< C-< >> and C<< C-> >> to set the mark at the beginning and end of the
file respectively.
-Also, Stefan Kangas sent in the following tip about an alternate
-approach using the command C<indent-region> to redo the indentation
-for the currently selected region (adhering to indent rules in the
-current major mode). You can run the command by typing M-x
-indent-region or pressing the default keybinding C-M-\ in cperl-mode.
+Also, Stefan Kangas sent in the following tip about an alternate
+approach using the command C<indent-region> to redo the indentation
+for the currently selected region (adhering to indent rules in the
+current major mode). You can run the command by typing M-x
+indent-region or pressing the default keybinding C-M-\ in cperl-mode.
Additional details can be found here:
-L<http://www.gnu.org/software/emacs/manual/html_node/emacs/Indentation-Commands.html>
+L<https://www.gnu.org/software/emacs/manual/html_node/emacs/Indentation-Commands.html>
=head1 APPENDIX 2: USING POSTGRESQL AND MYSQL
The main database used in this tutorial is the very simple yet powerful
-L<SQLite|http://www.sqlite.org>. This section provides information
+L<SQLite|https://www.sqlite.org>. This section provides information
that can be used to "convert" the tutorial to use
-L<PostgreSQL|http://www.postgresql.org> and
-L<MySQL|http://dev.mysql.com>. However, note that part of
+L<PostgreSQL|https://www.postgresql.org> and
+L<MySQL|https://dev.mysql.com>. However, note that part of
the beauty of the MVC architecture is that very little database-specific
code is spread throughout the system (at least when MVC is "done
right"). Consequently, converting from one database to another is
=head2 PostgreSQL
-Use the following steps to adapt the tutorial to PostgreSQL. Thanks
-to Caelum (Rafael Kitover) for assistance with the most recent
-updates, and Louis Moore, Marcello Romani and Tom Lanyon for help with
+Use the following steps to adapt the tutorial to PostgreSQL. Thanks
+to Caelum (Rafael Kitover) for assistance with the most recent
+updates, and Louis Moore, Marcello Romani and Tom Lanyon for help with
earlier versions.
=over 4
sudo aptitude install postgresql libdbd-pg-perl libdatetime-format-pg-perl
-To configure the permissions, you can open
-C</etc/postgresql/8.3/main/pg_hba.conf> and change this line (near the
+To configure the permissions, you can open
+F</etc/postgresql/8.3/main/pg_hba.conf> and change this line (near the
bottom):
# "local" is for Unix domain socket connections only
=item *
-Create the database and a user for the database (note that we are
-using "E<lt>catalystE<gt>" to represent the hidden password of
+Create the database and a user for the database (note that we are
+using "<catalyst>" to represent the hidden password of
"catalyst"):
$ sudo -u postgres createuser -P catappuser
=item *
-Open the C<myapp01_psql.sql> in your editor and enter:
+Open the F<myapp01_psql.sql> in your editor and enter:
--
-- Drops just in case you are reloading
DROP TABLE IF EXISTS users CASCADE;
DROP TABLE IF EXISTS roles CASCADE;
DROP TABLE IF EXISTS user_roles CASCADE;
-
+
--
-- Create a very simple database to hold book and author information
--
-- created TIMESTAMP NOT NULL DEFAULT now(),
-- updated TIMESTAMP
);
-
+
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
first_name TEXT,
last_name TEXT
);
-
+
-- 'book_authors' is a many-to-many join table between books & authors
CREATE TABLE book_authors (
book_id INTEGER REFERENCES books(id) ON DELETE CASCADE ON UPDATE CASCADE,
author_id INTEGER REFERENCES authors(id) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (book_id, author_id)
);
-
+
---
--- Load some sample data
---
Load the data:
$ psql -U catappuser -W catappdb -f myapp01_psql.sql
- Password for user catappuser:
+ Password for user catappuser:
psql:myapp01_psql.sql:8: NOTICE: CREATE TABLE will create implicit sequence "books_id_seq" for serial column "books.id"
psql:myapp01_psql.sql:8: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "books_pkey" for table "books"
CREATE TABLE
$ psql -U catappuser -W catappdb
Password for user catappuser: <catalyst>
Welcome to psql 8.3.7, the PostgreSQL interactive terminal.
-
+
Type: \copyright for distribution terms
\h for help with SQL commands
\? for help with psql commands
\g or terminate with semicolon to execute query
\q to quit
-
+
catappdb=> \dt
List of relations
- Schema | Name | Type | Owner
+ Schema | Name | Type | Owner
--------+--------------+-------+------------
public | authors | table | catappuser
public | book_authors | table | catappuser
public | books | table | catappuser
(3 rows)
-
+
catappdb=> select * from books;
- id | title | rating
+ id | title | rating
----+------------------------------------+--------
1 | CCSP SNRS Exam Certification Guide | 5
2 | TCP/IP Illustrated, Volume 1 | 5
4 | Perl Cookbook | 5
5 | Designing with Web Standards | 5
(5 rows)
-
- catappdb=>
+
+ catappdb=>
=back
After the steps where you:
edit lib/MyApp.pm
-
+
create lib/MyAppDB.pm
-
+
create lib/MyAppDB/Book.pm
-
+
create lib/MyAppDB/Author.pm
-
+
create lib/MyAppDB/BookAuthor.pm
Create the C<.sql> file for the user/roles data:
-Open C<myapp02_psql.sql> in your editor and enter:
+Open F<myapp02_psql.sql> in your editor and enter:
--
-- Add users and roles tables, along with a many-to-many join table
--
-
+
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username TEXT,
last_name TEXT,
active INTEGER
);
-
+
CREATE TABLE roles (
id SERIAL PRIMARY KEY,
role TEXT
);
-
+
CREATE TABLE user_roles (
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (user_id, role_id)
);
-
+
--
-- Load up some initial test data
--
- INSERT INTO users (username, password, email_address, first_name, last_name, active)
+ INSERT INTO users (username, password, email_address, first_name, last_name, active)
VALUES ('test01', 'mypass', 't01@na.com', 'Joe', 'Blow', 1);
- INSERT INTO users (username, password, email_address, first_name, last_name, active)
+ INSERT INTO users (username, password, email_address, first_name, last_name, active)
VALUES ('test02', 'mypass', 't02@na.com', 'Jane', 'Doe', 1);
INSERT INTO users (username, password, email_address, first_name, last_name, active)
VALUES ('test03', 'mypass', 't03@na.com', 'No', 'Go', 0);
$ psql -U catappuser -W catappdb -c "select * from users"
Password for user catappuser: <catalyst>
- id | username | password | email_address | first_name | last_name | active
+ id | username | password | email_address | first_name | last_name | active
----+----------+----------+---------------+------------+-----------+--------
1 | test01 | mypass | t01@na.com | Joe | Blow | 1
2 | test02 | mypass | t02@na.com | Jane | Doe | 1
=item *
-Modify C<set_hashed_passwords.pl> to match the following (the only difference
+Modify F<set_hashed_passwords.pl> to match the following (the only difference
is the C<connect> line):
#!/usr/bin/perl
-
+
use strict;
use warnings;
-
+
use MyApp::Schema;
-
+
my $schema = MyApp::Schema->connect('dbi:Pg:dbname=catappdb', 'catappuser', 'catalyst');
-
+
my @users = $schema->resultset('Users')->all;
-
+
foreach my $user (@users) {
$user->password('mypass');
$user->update;
}
-Run the C<set_hashed_passwords.pl> as per the "normal" flow of the
+Run the F<set_hashed_passwords.pl> as per the "normal" flow of the
tutorial:
$ perl -Ilib set_hashed_passwords.pl
=item *
-The Perl C<DBD::MySQL> module
+The Perl L<DBD::MySQL> module
=back
# mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
-
- Type 'help;' or '\h' for help. Type '\c' to clear the current input
+
+ Type 'help;' or '\h' for help. Type '\c' to clear the current input
statement.
-
+
mysql> SHOW VARIABLES LIKE 'have_innodb';
+---------------+-------+
| Variable_name | Value |
| have_innodb | YES |
+---------------+-------+
1 row in set (0.01 sec)
-
+
mysql> exit
Bye
# mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
-
+
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
-
+
mysql> CREATE DATABASE `myapp`;
Query OK, 1 row affected (0.01 sec)
-
+
mysql> GRANT ALL PRIVILEGES ON myapp.* TO 'tutorial'@'localhost' IDENTIFIED BY 'yourpassword';
Query OK, 0 rows affected (0.00 sec)
-
+
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)
-
+
mysql> exit
Bye
=item *
-Open the C<myapp01_mysql.sql> in your editor and enter:
+Open the F<myapp01_mysql.sql> in your editor and enter:
--
-- Create a very simple database to hold book and author information
--
CREATE TABLE IF NOT EXISTS `books` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `title` text CHARACTER SET utf8,
- `rating` int(11) DEFAULT NULL,
- PRIMARY KEY (`id`)
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `title` text CHARACTER SET utf8,
+ `rating` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 'book_authors' is a many-to-many join table between books & authors
CREATE TABLE IF NOT EXISTS `book_authors` (
- `book_id` int(11) NOT NULL DEFAULT '0',
- `author_id` int(11) NOT NULL DEFAULT '0',
- PRIMARY KEY (`book_id`,`author_id`),
- KEY `author_id` (`author_id`)
+ `book_id` int(11) NOT NULL DEFAULT '0',
+ `author_id` int(11) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`book_id`,`author_id`),
+ KEY `author_id` (`author_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `authors` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `first_name` text CHARACTER SET utf8,
- `last_name` text CHARACTER SET utf8,
- PRIMARY KEY (`id`)
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `first_name` text CHARACTER SET utf8,
+ `last_name` text CHARACTER SET utf8,
+ PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
---
--- Load some sample data
(3, 'Internetworking with TCP/IP Vol.1', 4),
(4, 'Perl Cookbook', 5),
(5, 'Designing with Web Standards', 5);
-
+
INSERT INTO `book_authors` (`book_id`, `author_id`) VALUES
(1, 1),
(1, 2),
(4, 6),
(4, 7),
(5, 8);
-
+
INSERT INTO `authors` (`id`, `first_name`, `last_name`) VALUES
(1, 'Greg', 'Bastien'),
(2, 'Sara', 'Nasseh'),
(6, 'Tom', 'Christiansen'),
(7, 'Nathan', 'Torkington'),
(8, 'Jeffrey', 'Zeldman');
-
+
ALTER TABLE `book_authors`
ADD CONSTRAINT `book_author_ibfk_2` FOREIGN KEY (`author_id`) REFERENCES `authors` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `book_author_ibfk_1` FOREIGN KEY (`book_id`) REFERENCES `books` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
$ mysql -u tutorial -p myapp
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
-
+
Welcome to the MySQL monitor. Commands end with ; or \g.
-
+
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
-
+
mysql> show tables;
+-----------------+
| Tables_in_myapp |
| books |
+-----------------+
3 rows in set (0.00 sec)
-
+
mysql> select * from books;
+----+------------------------------------+--------+
| id | title | rating |
| 5 | Designing with Web Standards | 5 |
+----+------------------------------------+--------+
5 rows in set (0.00 sec)
-
+
mysql>
=back
Regenerate the model using the Catalyst "_create.pl" script:
script/myapp_create.pl model DB DBIC::Schema MyApp::Schema create=static \
- dbi:mysql:myapp 'tutorial' 'yourpassword' '{ AutoCommit => 1 }'
+ dbi:mysql:myapp 'tutorial' 'yourpassword' '{ AutoCommit => 1 }'
=back
Create the C<.sql> file for the user/roles data:
-Open C<myapp02_mysql.sql> in your editor and enter:
+Open F<myapp02_mysql.sql> in your editor and enter:
--
-- Add users and roles tables, along with a many-to-many join table
--
CREATE TABLE IF NOT EXISTS `roles` (
- `id` int(11) NOT NULL,
- `role` text CHARACTER SET utf8,
- PRIMARY KEY (`id`)
+ `id` int(11) NOT NULL,
+ `role` text CHARACTER SET utf8,
+ PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `users` (
- `id` int(11) NOT NULL,
- `username` text CHARACTER SET utf8,
- `password` text CHARACTER SET utf8,
- `email_address` text CHARACTER SET utf8,
- `first_name` text CHARACTER SET utf8,
- `last_name` text CHARACTER SET utf8,
- `active` int(11) DEFAULT NULL,
- PRIMARY KEY (`id`)
+ `id` int(11) NOT NULL,
+ `username` text CHARACTER SET utf8,
+ `password` text CHARACTER SET utf8,
+ `email_address` text CHARACTER SET utf8,
+ `first_name` text CHARACTER SET utf8,
+ `last_name` text CHARACTER SET utf8,
+ `active` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `user_roles` (
- `user_id` int(11) NOT NULL DEFAULT '0',
- `role_id` int(11) NOT NULL DEFAULT '0',
- PRIMARY KEY (`user_id`,`role_id`),
- KEY `role_id` (`role_id`)
+ `user_id` int(11) NOT NULL DEFAULT '0',
+ `role_id` int(11) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`user_id`,`role_id`),
+ KEY `role_id` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Load up some initial test data
INSERT INTO `roles` (`id`, `role`) VALUES
(1, 'user'),
(2, 'admin');
-
+
INSERT INTO `users` (`id`, `username`, `password`, `email_address`, `first_name`, `last_name`, `active`) VALUES
(1, 'test01', 'mypass', 't01@na.com', 'Joe', 'Blow', 1),
(2, 'test02', 'mypass', 't02@na.com', 'Jane', 'Doe', 1),
(3, 'test03', 'mypass', 't03@na.com', 'No', 'Go', 0);
-
+
INSERT INTO `user_roles` (`user_id`, `role_id`) VALUES
(1, 1),
(2, 1),
(3, 1),
(1, 2);
-
+
ALTER TABLE `user_roles
ADD CONSTRAINT `user_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `user_role_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
Regenerate the model using the Catalyst "_create.pl" script:
script/myapp_create.pl model DB DBIC::Schema MyApp::Schema create=static \
- components=TimeStamp,PassphraseColumn dbi:mysql:myapp 'tutorial' 'yourpassword' '{ AutoCommit => 1 }'
+ components=TimeStamp,PassphraseColumn dbi:mysql:myapp 'tutorial' 'yourpassword' '{ AutoCommit => 1 }'
=back
Create the C<.sql> file for the hashed password data:
-Open C<myapp03_mysql.sql> in your editor and enter:
+Open F<myapp03_mysql.sql> in your editor and enter:
--
-- Convert passwords to SHA-1 hashes
Copyright 2006-2011, Kennedy Clark, under the
Creative Commons Attribution Share-Alike License Version 3.0
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
+(L<https://creativecommons.org/licenses/by-sa/3.0/us/>).