=head1 NAME
Catalyst::Manual::Tutorial::CatalystBasics - Catalyst Tutorial - Part 2: Catalyst Application Development Basics
=head1 OVERVIEW
This is B<Part 2 of 9> for the Catalyst tutorial.
L<Tutorial Overview|Catalyst::Manual::Tutorial>
=over 4
=item 1
L<Introduction|Catalyst::Manual::Tutorial::Intro>
=item 2
B<Catalyst Basics>
=item 3
L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
=item 4
L<Authentication|Catalyst::Manual::Tutorial::Authentication>
=item 5
L<Authorization|Catalyst::Manual::Tutorial::Authorization>
=item 6
L<Debugging|Catalyst::Manual::Tutorial::Debugging>
=item 7
L<Testing|Catalyst::Manual::Tutorial::Testing>
=item 8
L<Advanced CRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
=item 9
L<Appendices|Catalyst::Manual::Tutorial::Appendices>
=back
=head1 DESCRIPTION
In this part of the tutorial, we will create a very basic Catalyst web
application. Though simple in many respects, this section will already
demonstrate a number of powerful capabilities such as:
=over 4
=item * Helper Scripts
Catalyst helper scripts that can be used to rapidly bootstrap the
skeletal structure of an application.
=item * MVC
Model/View/Controller (MVC) provides an architecture that facilitates a
clean "separation of control" between the different portions of your
application. Given that many other documents cover this subject in
detail, MVC will not be discussed in depth here (for an excellent
introduction to MVC and general Catalyst concepts, please see
L<Catalyst::Manual::About>. In short:
=over 4
=item * Model
The model usually represents a data store. In most applications, the
model equates to the objects that are created from and saved to your SQL
database.
=item * View
The view takes model objects and renders them into something for the end
user to look at. Normally this involves a template-generation tool that
creates HTML for the user's web browser, but it could easily be code
that generates other forms such as PDF documents, e-mails, or Excel
spreadsheets.
=item * Controller
As suggested by its name, the controller takes user requests and routes
them to the necessary model and view.
=back
=item * ORM
The use of Object-Relational Mapping (ORM) technology for database
access. Specifically, ORM provides an automated and standardized means
to persist and restore objects to/from a relational database.
=back
B<TIP>: Note that all of the code for this part of the tutorial can be
pulled from the Catalyst Subversion repository in one step with the
following command:
svn co http://dev.catalyst.perl.org/repos/Catalyst/tags/examples/Tutorial/MyApp/5.7/CatalystBasics MyApp
=head1 CREATE A CATALYST PROJECT
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.
In the case of this tutorial, use the Catalyst C<catalyst.pl> script to
initialize the framework for an application called C<MyApp>:
$ catalyst.pl MyApp
created "MyApp"
created "MyApp/script"
created "MyApp/lib"
created "MyApp/root"
...
created "MyApp/script/myapp_create.pl"
$ cd MyApp
The C<catalyst.pl> helper script will display the names of the
directories and files it creates.
Though it's too early for any significant celebration, we already have a
functioning application. Run the following command to run this
application with the built-in development web server:
$ script/myapp_server.pl
[debug] Debug messages enabled
[debug] Loaded plugins:
.----------------------------------------------------------------------------.
| Catalyst::Plugin::ConfigLoader 0.06 |
| Catalyst::Plugin::Static::Simple 0.14 |
'----------------------------------------------------------------------------'
[debug] Loaded dispatcher "Catalyst::Dispatcher"
[debug] Loaded engine "Catalyst::Engine::HTTP"
[debug] Found home "/root/dev/MyApp"
[debug] Loaded components:
.-----------------------------------------------------------------+----------.
| Class | Type |
+-----------------------------------------------------------------+----------+
| MyApp::Controller::Root | instance |
'-----------------------------------------------------------------+----------'
[debug] Loaded Private actions:
.----------------------+--------------------------------------+--------------.
| Private | Class | Method |
+----------------------+--------------------------------------+--------------+
| /default | MyApp::Controller::Root | default |
| /end | MyApp::Controller::Root | end |
'----------------------+--------------------------------------+--------------'
[info] MyApp powered by Catalyst 5.7000
You can connect to your server at http://localhost.localdomain:3000
Point your web browser to L<http://localhost:3000> (substituting a
different hostname or IP address as appropriate) and you should be
greeted by the Catalyst welcome screen. Information similar to the
following should be appended to the logging output of the development
server:
[info] *** Request 1 (0.043/s) [6003] [Fri Jul 7 13:32:53 2006] ***
[debug] "GET" request for "/" from "127.0.0.1"
[info] Request took 0.067675s (14.777/s)
.----------------------------------------------------------------+-----------.
| Action | Time |
+----------------------------------------------------------------+-----------+
| /default | 0.002844s |
| /end | 0.000207s |
'----------------------------------------------------------------+-----------'
Press Ctrl-C to break out of the development server.
=head1 CREATE A SQLITE DATABASE
In this step, we make a text file with the required SQL commands to
create a database table and load some sample data. Open C<myapp01.sql>
in your editor and enter:
--
-- Create a very simple database to hold book and author information
--
CREATE TABLE books (
id INTEGER PRIMARY KEY,
title TEXT ,
rating INTEGER
);
-- 'book_authors' is a many-to-many join table between books & authors
CREATE TABLE book_authors (
book_id INTEGER,
author_id INTEGER,
PRIMARY KEY (book_id, author_id)
);
CREATE TABLE authors (
id INTEGER PRIMARY KEY,
first_name TEXT,
last_name TEXT
);
---
--- Load some sample data
---
INSERT INTO books VALUES (1, 'CCSP SNRS Exam Certification Guide', 5);
INSERT INTO books VALUES (2, 'TCP/IP Illustrated, Volume 1', 5);
INSERT INTO books VALUES (3, 'Internetworking with TCP/IP Vol.1', 4);
INSERT INTO books VALUES (4, 'Perl Cookbook', 5);
INSERT INTO books VALUES (5, 'Designing with Web Standards', 5);
INSERT INTO authors VALUES (1, 'Greg', 'Bastien');
INSERT INTO authors VALUES (2, 'Sara', 'Nasseh');
INSERT INTO authors VALUES (3, 'Christian', 'Degu');
INSERT INTO authors VALUES (4, 'Richard', 'Stevens');
INSERT INTO authors VALUES (5, 'Douglas', 'Comer');
INSERT INTO authors VALUES (6, 'Tom', 'Christiansen');
INSERT INTO authors VALUES (7, ' Nathan', 'Torkington');
INSERT INTO authors VALUES (8, 'Jeffrey', 'Zeldman');
INSERT INTO book_authors VALUES (1, 1);
INSERT INTO book_authors VALUES (1, 2);
INSERT INTO book_authors VALUES (1, 3);
INSERT INTO book_authors VALUES (2, 4);
INSERT INTO book_authors VALUES (3, 5);
INSERT INTO book_authors VALUES (4, 6);
INSERT INTO book_authors VALUES (4, 7);
INSERT INTO book_authors VALUES (5, 8);
B<TIP>: See Appendix 1 for tips on removing the leading spaces when
cutting and pasting example code from POD-based documents.
Then use the following command to build a C<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 < myapp01.sql> command.
Once the C<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:
$ sqlite3 myapp.db
SQLite version 3.2.2
Enter ".help" for instructions
sqlite> select * from books;
1|CCSP SNRS Exam Certification Guide|5
2|TCP/IP Illustrated, Volume 1|5
3|Internetworking with TCP/IP Vol.1|4
4|Perl Cookbook|5
5|Designing with Web Standards|5
sqlite> .q
$
Or:
$ sqlite3 myapp.db "select * from books"
1|CCSP SNRS Exam Certification Guide|5
2|TCP/IP Illustrated, Volume 1|5
3|Internetworking with TCP/IP Vol.1|4
4|Perl Cookbook|5
5|Designing with Web Standards|5
As with most other SQL tools, if you are using the full "interactive"
environment you need to terminate your SQL commands with a ";" (it's not
required if you do a single SQL statement on the command line). Use
".q" to exit from SQLite from the SQLite interactive mode and return to
your OS command prompt.
=head1 EDIT THE LIST OF CATALYST PLUGINS
One of the greatest benefits of Catalyst is that it has such a large
library of plugins available. Plugins are used to seamlessly integrate
existing Perl modules into the overall Catalyst framework. In general,
they do this by adding additional methods to the C<context> object
(generally written as C<$c>) that Catalyst passes to every component
throughout the framework.
By default, Catalyst enables three plugins/flags:
=over 4
=item *
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
this plugin when you place your application into production.
As you may have noticed, C<-Debug> is not a plugin, but a I<flag>.
Although most of the items specified on the C<use 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 C<Catalyst.pm> to get details on other flags
(currently C<-Engine>, C<-Home>, and C<-Log>).
If you prefer, you can use the C<$c-E<gt>debug> method to enable debug
messages.
=item *
L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader>
C<ConfigLoader> provides an automatic way to load configurable
parameters for your application from a central YAML file (versus having
the values hard-coded inside your Perl modules). If you have not been
exposed to YAML before, it is a human-readable data serialization format
that can be used to read (and write) values to/from text files. We will
see how to use this feature of Catalyst during the authentication and
authorization sections (Part 4 and Part 5).
=item *
L<Catalyst::Plugin::Static::Simple|Catalyst::Plugin::Static::Simple>
C<Static::Simple> provides an easy method of serving static content such
as images and CSS files under the development server.
=back
To modify the list of plugins, edit C<lib/MyApp.pm> (this file is
generally referred to as your I<application class>) and delete the line
with:
use Catalyst qw/-Debug ConfigLoader Static::Simple/;
Replace it with:
use Catalyst qw/
-Debug
ConfigLoader
Static::Simple
StackTrace
/;
This tells Catalyst to start using one new plugin:
=over 4
=item *
L<Catalyst::Plugin::StackTrace|Catalyst::Plugin::StackTrace>
Adds a stack trace to the standard Catalyst "debug screen" (this is the
screen Catalyst sends to your browser when an error occurs).
Note: L<StackTrace|Catalyst::Plugin::StackTrace> output appears in your
browser, not in the console window from which you're running your
application, which is where logging output usually goes.
=back
Note that when specifying plugins on the C<use Catalyst> line, you can
omit C<Catalyst::Plugin::> from the name. Additionally, you can spread
the plugin names across multiple lines as shown here, or place them all
on one (or more) lines as with the default configuration.
B<TIP:> You may see examples that include the
L<Catalyst::Plugin::DefaultEnd|Catalyst::Plugin::DefaultEnd>
plugins. As of Catalyst 5.7000, C<DefaultEnd> has been
deprecated in favor of
L<Catalyst::Action::RenderView|Catalyst::Action::RenderView>
(as the name of the package suggests, C<RenderView> is not
a plugin, but an action). The purpose of both is essentially the same:
forward processing to the view to be rendered. Applications generated
under 5.7000 should automatically use C<RenderView> and "just work"
for most applications. For more information on C<RenderView> and
the various options for forwarding to your view logic, please refer
to the "Using RenderView for the Default View" section under
"CATALYST VIEWS" below.
=head1 DATABASE ACCESS WITH C<DBIx::Class>
Catalyst can be used with virtually any form of persistent datastore
available via Perl. For example,
L<Catalyst::Model::DBI|Catalyst::Model::DBI> can be used to
easily access databases through the traditional Perl C<DBI> interface.
However, most Catalyst applications use some form of ORM technology to
automatically create and save model objects as they are used. Although
Tony Bowden's L<Class::DBI|Class::DBI> has been the traditional
Perl ORM engine, Matt Trout's L<DBIx::Class|DBIx::Class> (abbreviated
as "DBIC") has rapidly emerged as the Perl-based ORM technology of choice.
Most new Catalyst applications rely on DBIC, as will this tutorial.
Note: See L<Catalyst:: Model::CDBI> for more information on using
Catalyst with L<Class::DBI|Class::DBI>.
=head2 Create a DBIC Schema File
DBIx::Class uses a schema file to load other classes that represent the
tables in your database (DBIC refers to these "table objects" as "result
sources"; see L<DBIx::Class::ResultSource>). In this case, we want to
load the model object for the C<books>, C<book_authors>, and C<authors>
tables created in the previous step.
Open C<lib/MyAppDB.pm> in your editor and insert:
package MyAppDB;
=head1 NAME
MyAppDB - DBIC Schema Class
=cut
# Our schema needs to inherit from 'DBIx::Class::Schema'
use base qw/DBIx::Class::Schema/;
# Need to load the DB Model classes here.
# You can use this syntax if you want:
# __PACKAGE__->load_classes(qw/Book BookAuthor Author/);
# Also, if you simply want to load all of the classes in a directory
# of the same name as your schema class (as we do here) you can use:
# __PACKAGE__->load_classes(qw//);
# But the variation below is more flexible in that it can be used to
# load from multiple namespaces.
__PACKAGE__->load_classes({
MyAppDB => [qw/Book BookAuthor Author/]
});
1;
B<Note:> C<__PACKAGE__> is just a shorthand way of referencing the name
of the package where it is used. Therefore, in C<MyAppDB.pm>,
C<__PACKAGE__> is equivalent to C<MyAppDB>.
=head2 Create the DBIC "Result Source" Files
In this step, we create "table classes" (again, these are called a
"result source" classes in DBIC) that act as model objects for the
C<books>, C<book_authors>, and C<authors> tables in our database.
First, create a directory to hold the class:
$ mkdir lib/MyAppDB
Then open C<lib/MyAppDB/Book.pm> in your editor and enter:
package MyAppDB::Book;
use base qw/DBIx::Class/;
# Load required DBIC stuff
__PACKAGE__->load_components(qw/PK::Auto Core/);
# Set the table name
__PACKAGE__->table('books');
# Set columns in table
__PACKAGE__->add_columns(qw/id title rating/);
# Set the primary key for the table
__PACKAGE__->set_primary_key(qw/id/);
#
# Set relationships:
#
# has_many():
# args:
# 1) Name of relationship, DBIC will create accessor with this name
# 2) Name of the model class referenced by this relationship
# 3) Column name in *foreign* table
__PACKAGE__->has_many(book_authors => 'MyAppDB::BookAuthor', 'book_id');
# many_to_many():
# args:
# 1) Name of relationship, DBIC will create accessor with this name
# 2) Name of has_many() relationship this many_to_many() is shortcut for
# 3) Name of belongs_to() relationship in model class of has_many() above
# You must already have the has_many() defined to use a many_to_many().
__PACKAGE__->many_to_many(authors => 'book_authors', 'author');
=head1 NAME
MyAppDB::Book - A model object representing a book.
=head1 DESCRIPTION
This is an object that represents a row in the 'books' table of your application
database. It uses DBIx::Class (aka, DBIC) to do ORM.
For Catalyst, this is designed to be used through MyApp::Model::MyAppDB.
Offline utilities may wish to use this class directly.
=cut
1;
This defines both a C<has_many> and a C<many_to_many> relationship. The
C<many_to_many> relationship is optional, but it makes it easier to map
a book to its collection of authors. Without it, we would have to
"walk" though the C<book_authors> table as in
C<$book-E<gt>book_authors-E<gt>first-E<gt>author-E<gt>last_name> (we
will see examples on how to use DBIC objects in your code soon, but note
that because C<$book-E<gt>book_authors> 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>authors-E<gt>first-E<gt>last_name>.
Note that you cannot define a C<many_to_many> relationship without also
having the C<has_many> relationship in place.
Next, open C<lib/MyAppDB/Author.pm> in your editor and enter:
package MyAppDB::Author;
use base qw/DBIx::Class/;
# Load required DBIC stuff
__PACKAGE__->load_components(qw/PK::Auto Core/);
# Set the table name
__PACKAGE__->table('authors');
# Set columns in table
__PACKAGE__->add_columns(qw/id first_name last_name/);
# Set the primary key for the table
__PACKAGE__->set_primary_key(qw/id/);
#
# Set relationships:
#
# has_many():
# args:
# 1) Name of relationship, DBIC will create accessor with this name
# 2) Name of the model class referenced by this relationship
# 3) Column name in *foreign* table
__PACKAGE__->has_many(book_author => 'MyAppDB::BookAuthor', 'author_id');
# many_to_many():
# args:
# 1) Name of relationship, DBIC will create accessor with this name
# 2) Name of has_many() relationship this many_to_many() is shortcut for
# 3) Name of belongs_to() relationship in model class of has_many() above
# You must already have the has_many() defined to use a many_to_many().
__PACKAGE__->many_to_many(books => 'book_author', 'book');
=head1 NAME
MyAppDB::Author - A model object representing an author of a book (if a book has
multiple authors, each will be represented be separate Author object).
=head1 DESCRIPTION
This is an object that represents a row in the 'authors' table of your application
database. It uses DBIx::Class (aka, DBIC) to do ORM.
For Catalyst, this is designed to be used through MyApp::Model::MyAppDB.
Offline utilities may wish to use this class directly.
=cut
1;
Finally, open C<lib/MyAppDB/BookAuthor.pm> in your editor and enter:
package MyAppDB::BookAuthor;
use base qw/DBIx::Class/;
# Load required DBIC stuff
__PACKAGE__->load_components(qw/PK::Auto Core/);
# Set the table name
__PACKAGE__->table('book_authors');
# Set columns in table
__PACKAGE__->add_columns(qw/book_id author_id/);
# Set the primary key for the table
__PACKAGE__->set_primary_key(qw/book_id author_id/);
#
# Set relationships:
#
# belongs_to():
# args:
# 1) Name of relationship, DBIC will create accessor with this name
# 2) Name of the model class referenced by this relationship
# 3) Column name in *this* table
__PACKAGE__->belongs_to(book => 'MyAppDB::Book', 'book_id');
# belongs_to():
# args:
# 1) Name of relationship, DBIC will create accessor with this name
# 2) Name of the model class referenced by this relationship
# 3) Column name in *this* table
__PACKAGE__->belongs_to(author => 'MyAppDB::Author', 'author_id');
=head1 NAME
MyAppDB::BookAuthor - A model object representing the JOIN between an author and
a book.
=head1 DESCRIPTION
This is an object that represents a row in the 'book_authors' table of your
application database. It uses DBIx::Class (aka, DBIC) to do ORM.
You probably won't need to use this class directly -- it will be automatically
used by DBIC where joins are needed.
For Catalyst, this is designed to be used through MyApp::Model::MyAppDB.
Offline utilities may wish to use this class directly.
=cut
1;
B<Note:> This sample application uses a plural form for the database
tables (e.g., C<books> and C<authors>) and a singular form for the model
objects (e.g., C<Book> and C<Author>); however, Catalyst places no
restrictions on the naming conventions you wish to use.
=head2 Use C<Catalyst::Model::DBIC::Schema> To Load The Model Class
When L<Catalyst::Model::DBIC::Schema|Catalyst::Model::DBIC::Schema> is
in use, Catalyst essentially reads an existing copy of your database
model and creates a new set of objects under C<MyApp::Model> for use
inside of Catalyst.
B<Note:> With
L<Catalyst::Model::DBIC::Schema|Catalyst::Model::DBIC::Schema> you
essentially end up with two sets of model classes (only one of which
you write... the other set is created automatically in memory when
your Catalyst application initializes). For this tutorial application,
the important points to remember are: you write the I<result source>
files in C<MyAppDB>, but I<within Catalyst> you use the I<automatically
created model classes> in C<MyApp::Model>.
Use the
L<Catalyst::Helper::Model::DBIC::Schema|Catalyst::Helper::Model::DBIC::Schema>
helper script to create the model class that loads up the model we
created in the previous step:
$ script/myapp_create.pl model MyAppDB DBIC::Schema MyAppDB dbi:SQLite:myapp.db '' '' '{ AutoCommit => 1 }'
exists "/root/dev/MyApp/script/../lib/MyApp/Model"
exists "/root/dev/MyApp/script/../t"
created "/root/dev/MyApp/script/../lib/MyApp/Model/MyAppDB.pm"
created "/root/dev/MyApp/script/../t/model_MyAppDB.t"
Where the first C<MyAppDB> is the name of the class to be created by the
helper in C<lib/MyApp/Model> and the second C<MyAppDB> is the name of
existing schema file we created (in C<lib/MyAppDB.pm>). You can see
that the helper creates a model file under C<lib/MyApp/Model> (Catalyst
has a separate directory under C<lib/MyApp> for each of the three parts
of MVC: C<Model>, C<View>, and C<Controller> [although older Catalyst
applications often use the directories C<M>, C<V>, and C<C>]).
=head1 CREATE A CATALYST CONTROLLER
Controllers are where you write methods that interact with user
input--typically, controller methods respond to C<GET> and C<POST>
messages from the user's web browser.
Use the Catalyst C<create> script to add a controller for book-related
actions:
$ script/myapp_create.pl controller Books
exists "/root/dev/MyApp/script/../lib/MyApp/Controller"
exists "/root/dev/MyApp/script/../t"
created "/root/dev/MyApp/script/../lib/MyApp/Controller/Books.pm"
created "/root/dev/MyApp/script/../t/controller_Books.t"
Then edit C<lib/MyApp/Controller/Books.pm> 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('MyAppDB::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';
}
B<Note:> Programmers experienced with object-oriented Perl should
recognize C<$self> as a reference to the object where this method was
called. On the other hand, C<$c> will be new to many Perl programmers
who have not used Catalyst before (it's sometimes written as
C<$context>). The Context object is automatically passed to all
Catalyst components. It is used to pass information between components
and provide access to Catalyst and plugin functionality.
B<TIP>: You may see the C<$c-E<gt>model('MyAppDB::Book')> used above
written as C<$c-E<gt>model('MyAppDB')-E<gt>resultset('Book)>. The two
are equivalent.
B<Note:> Catalyst actions are regular Perl methods, but they make use of
Nicholas Clark's C<attributes> module (that's the C<: Local> next to the
C<sub list> in the code above) to provide additional information to the
Catalyst dispatcher logic.
=head1 CATALYST VIEWS
Views are where you render output, typically for display in the user's
web browser, but also possibly using other display output-generation
systems. As with virtually every aspect of Catalyst, options abound
when it comes to the specific view technology you adopt inside your
application. However, most Catalyst applications use the Template
Toolkit, known as TT (for more information on TT, see
L<http://www.template-toolkit.org>). Other popular view technologies
include Mason (L<http://www.masonhq.com> and
L<http://www.masonbook.com>) and L<HTML::Template|HTML::Template>
(L<http://html-template.sourceforge.net>).
=head2 Create a Catalyst View Using C<TTSITE>
When using TT for the Catalyst view, there are two main helper scripts:
=over 4
=item *
L<Catalyst::Helper::View::TT|Catalyst::Helper::View::TT>
=item *
L<Catalyst::Helper::View::TTSite|Catalyst::Helper::View::TTSite>
=back
Both are similar, but C<TT> merely creates the C<lib/MyApp/View/TT.pm>
file and leaves the creation of any hierarchical template organization
entirely up to you. (It also creates a C<t/view_TT.t> file for testing;
test cases will be discussed in Part 7). The C<TTSite> helper creates a
modular and hierarchical view layout with separate Template Toolkit (TT)
files for common header and footer information, configuration values, a
CSS stylesheet, and more.
Enter the following command to enable the C<TTSite> style of view
rendering for this tutorial:
$ script/myapp_create.pl view TT TTSite
exists "/root/dev/MyApp/script/../lib/MyApp/View"
exists "/root/dev/MyApp/script/../t"
created "/root/dev/MyApp/script/../lib/MyApp/View/TT.pm"
created "/root/dev/MyApp/script/../root/lib"
...
created "/root/dev/MyApp/script/../root/src/ttsite.css"
This puts a number of files in the C<root/lib> and C<root/src>
directories that can be used to customize the look and feel of your
application. Also take a look at C<lib/MyApp/View/TT.pm> for config
values set by the C<TTSite> helper.
B<TIP>: Note that TTSite does one thing that could confuse people who
are used to the normal C<TT> Catalyst view: it redefines the Catalyst
context object in templates from its usual C<c> to C<Catalyst>. When
looking at other Catalyst examples, remember that they almost always use
C<c>. Note that Catalyst and TT I<do not complain> when you use the
wrong name to access the context object...TT simply outputs blanks for
that bogus logic (see next tip to change this behavior with TT C<DEBUG>
options). Finally, be aware that this change in name I<only>
applies to how the context object is accessed inside your TT templates;
your controllers will continue to use C<$c> (or whatever name you use
when fetching the reference from C<@_> inside your methods). (You can
change back to the "default" behavior be removing the C<CATALYST_VAR>
line from C<lib/MyApp/View/TT.pm>, but you will also have to edit
C<root/lib/config/main> and C<root/lib/config/url>. If you do this, be
careful not to have a collision between your own C<c> variable and the
Catalyst C<c> variable.)
B<TIP>: When troubleshooting TT it can be helpful to enable variable
C<DEBUG> options. You can do this in a Catalyst environment by adding
a C<DEBUG> line to the C<__PACKAGE__->config> declaration in
C<MyApp/View/TT.pm>:
__PACKAGE__->config({
CATALYST_VAR => 'Catalyst',
...
DEBUG => 'undef',
...
});
There are a variety of options you can use, such as 'undef', 'all',
'service', 'context', 'parser', 'provider', and 'service'. See
L<Template::Constants> for more information (remove the C<DEBUG_>
portion of the name shown in the TT docs and convert to lower case
for use inside Catalyst).
=head2 Using C<RenderView> for the Default View
Once your controller logic has processed the request from a user, it
forwards processing to your view in order to generate the appropriate
response output. Catalyst v5.7000 ships with a new mechanism,
L<Catalyst::Action::RenderView|Catalyst::Action::RenderView>, that
automatically performs this operation. If you look in
C<lib/MyApp/Controller/Root.pm>, you should see the this empty
definition for the C<sub end> method:
sub end : ActionClass('RenderView') {}
The following bullet points provide a quick overview of the
C<RenderView> process:
=over 4
=item *
C<Root.pm> is designed to hold application-wide logic.
=item *
At the end of a given user request, Catalyst will call the most specific
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.
=item *
Because the definition includes an C<ActionClass> attribute, the
L<Catalyst::Action::RenderView|Catalyst::Action::RenderView> logic
will be executed B<after> any code inside the definition of C<sub end>
is run. See L<Catalyst::Manual::Actions|Catalyst::Manual::Actions>
for more information on C<ActionClass>.
=item *
Because C<sub end> is empty, this effectively just runs the default
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
L<Catalyst::Action::RenderView|Catalyst::Action::RenderView> for more
detailed information on how to extended C<RenderView> in C<sub end>.
=back
=head3 The History Leading Up To C<RenderView>
Although C<RenderView> strikes a nice balance between default
behavior and easy extensibility, it is a new feature that won't
appear in most existing Catalyst examples. This section provides
some brief background on the evolution of default view rendering
logic with an eye to how they can be migrated to C<RenderView>:
=over 4
=item *
Private C<end> Action in Application Class
Older Catalyst-related documents often suggest that you add a "private
end action" to your application class (C<MyApp.pm>) or Root.pm
(C<MyApp/Controller/Root.pm>). These examples should be easily
converted to L<RenderView|Catalyst::Action::RenderView> by simply adding
C<ActionClass('RenderView')> to the C<sub end> definition. If end sub is
defined in your application class (C<MyApp.pm>), you should also migrate
it to C<MyApp/Controller/Root.pm>.
=item *
L<Catalyst::Plugin::DefaultEnd|Catalyst::Plugin::DefaultEnd>
C<DefaultEnd> represented the "next step" in passing processing from
your controller to your view. It has the advantage of only requiring
that C<DefaultEnd> be added to the list of plugins in C<lib/MyApp.pm>.
It also allowed you to add "dump_info=1" (precede with "?" or "&"
depending on where it is in the URL) to I<force> the debug screen at the
end of the Catalyst request processing cycle. However, it was more
difficult to extend than the C<RenderView> mechanism, and is now
deprecated.
=item *
L<Catalyst::Action::RenderView|Catalyst::Action::RenderView>
As discussed above, the current recommended approach to handling your
view logic relies on
L<Catalyst::Action::RenderView|Catalyst::Action::RenderView>. Although
similar in first appearance to the "private end action" approach, it
utilizes Catalyst's "ActionClass" mechanism to provide both automatic
default behavior (you don't have to include a plugin as with
C<DefaultEnd>) and easy extensibility. As with C<DefaultEnd>, it allows
you to add "dump_info=1" (precede with "?" or "&" depending on where it
is in the URL) to I<force> the debug screen at the end of the Catalyst
request processing cycle.
=back
It is recommended that all Catalyst applications use or migrate to
the C<RenderView> approach.
=head2 Globally Customize Every View
When using TTSite, files in the subdirectories of C<root/lib> can be
used to make changes that will appear in every view. For example, to
display optional status and error messages in every view, edit
C<root/lib/site/layout>, updating it to match the following (the two HTML
C<span> elements are new):
<div id="header">[% PROCESS site/header %]</div>
<div id="content">
<span class="message">[% status_msg %]</span>
<span class="error">[% error_msg %]</span>
[% content %]
</div>
<div id="footer">[% PROCESS site/footer %]</div>
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
be displayed whenever any view used by that request is rendered. The
C<message> and C<error> CSS styles are automatically defined in
C<root/src/ttsite.css> and can be customized to suit your needs.
B<Note:> The Catalyst stash only lasts for a single HTTP request. If
you need to retain information across requests you can use
L<Catalyst::Plugin::Session|Catalyst::Plugin::Session> (we will use
Catalyst sessions in the Authentication part of the tutorial).
=head2 Create a TT Template Page
To add a new page of content to the TTSite view hierarchy, just create a
new C<.tt2> file in C<root/src>. Only include HTML markup that goes
inside the HTML <body> and </body> tags, TTSite will use the contents of
C<root/lib/site> to add the top and bottom.
First create a directory for book-related TT templates:
$ mkdir root/src/books
Then open C<root/src/books/list.tt2> in your editor and enter:
[% # This is a TT comment. The '-' at the end "chomps" the newline. You won't -%]
[% # see this "chomping" in your browser because HTML ignores blank lines, but -%]
[% # it WILL eliminate a blank line if you view the HTML source. It's purely -%]
[%- # optional, but both the beginning and the ending TT tags support chomping. -%]
[% # Provide a title to root/lib/site/header -%]
[% META title = 'Book List' -%]
<table>
<tr><th>Title</th><th>Rating</th><th>Author(s)</th></tr>
[% # Display each book in a table row %]
[% FOREACH book IN books -%]
<tr>
<td>[% book.title %]</td>
<td>[% book.rating %]</td>
<td>
[% # First initialize a TT variable to hold a list. Then use a TT FOREACH -%]
[% # loop in 'side effect notation' to load just the last names of the -%]
[% # authors into the list. Note that the 'push' TT vmethod does not -%]
[% # a value, so nothing will be printed here. But, if you have something -%]
[% # in TT that does return a method and you don't want it printed, you -%]
[% # can: 1) assign it to a bogus value, or 2) use the CALL keyword to -%]
[% # call it and discard the return value. -%]
[% tt_authors = [ ];
tt_authors.push(author.last_name) FOREACH author = book.authors %]
[% # Now use a TT 'virtual method' to display the author count in parens -%]
([% tt_authors.size %])
[% # Use another TT vmethod to join & print the names & comma separators -%]
[% tt_authors.join(', ') %]
</td>
</tr>
[% END -%]
</table>
As indicated by the inline comments above, the C<META title> line uses
TT's META feature to provide a title to C<root/lib/site/header>.
Meanwhile, the outer C<FOREACH> loop iterates through each C<book> model
object and prints the C<title> and C<rating> fields. An inner
C<FOREACH> loop prints the last name of each author in a comma-separated
list within a single table cell.
If you are new to TT, the C<[%> and C<%]> tags are used to delimit TT
code. TT supports a wide variety of directives for "calling" other
files, looping, conditional logic, etc. In general, TT simplifies the
usual range of Perl operators down to the single dot (C<.>) operator.
This applies to operations as diverse as method calls, hash lookups, and
list index values (see
L<http://www.template-toolkit.org/docs/default/Manual/Variables.html>
for details and examples). In addition to the usual C<Template> module
Pod documentation, you can access the TT manual at
L<http://www.template-toolkit.org/docs/default/>.
B<NOTE>: The C<TTSite> helper creates several TT files using an
extension of C<.tt2>. Most other Catalyst and TT examples use an
extension of C<.tt>. You can use either extension (or no extension at
all) with TTSite and TT, just be sure to use the appropriate extension
for both the file itself I<and> the C<$c-E<gt>stash-E<gt>{template} =
...> line in your controller. This document will use C<.tt2> for
consistency with the files already created by the C<TTSite> helper.
=head1 RUN THE APPLICATION
First, let's enable an environment variable option that causes
DBIx::Class to dump the SQL statements it's using to access the database
(this option can provide extremely helpful troubleshooting information):
$ export DBIX_CLASS_STORAGE_DBI_DEBUG=1
This assumes you are using BASH as your shell -- adjust accordingly if
you are using a different shell (for example, under tcsh, use
C<setenv DBIX_CLASS_STORAGE_DBI_DEBUG 1>).
B<NOTE>: You can also set this in your code using
C<$class-E<gt>storage-E<gt>debug(1);>. See
L<DBIx::Class::Manual::Troubleshooting> for details (including options
to log to file instead of displaying to the Catalyst development server
log).
Then run the Catalyst "demo server" script:
$ script/myapp_server.pl
Your development server log output should display something like:
$ script/myapp_server.pl
[debug] Debug messages enabled
[debug] Loaded plugins:
.----------------------------------------------------------------------------.
| Catalyst::Plugin::ConfigLoader 0.06 |
| Catalyst::Plugin::StackTrace 0.04 |
| Catalyst::Plugin::Static::Simple 0.14 |
'----------------------------------------------------------------------------'
[debug] Loaded dispatcher "Catalyst::Dispatcher"
[debug] Loaded engine "Catalyst::Engine::HTTP"
[debug] Found home "/home/me/MyApp"
[debug] Loaded components:
.-----------------------------------------------------------------+----------.
| Class | Type |
+-----------------------------------------------------------------+----------+
| MyApp::Controller::Books | instance |
| MyApp::Controller::Root | instance |
| MyApp::Model::MyAppDB | instance |
| MyApp::Model::MyAppDB::Author | class |
| MyApp::Model::MyAppDB::Book | class |
| MyApp::Model::MyAppDB::BookAuthor | class |
| MyApp::View::TT | instance |
'-----------------------------------------------------------------+----------'
[debug] Loaded Private actions:
.----------------------+--------------------------------------+--------------.
| Private | Class | Method |
+----------------------+--------------------------------------+--------------+
| /default | MyApp::Controller::Root | default |
| /end | MyApp::Controller::Root | end |
| /books/index | MyApp::Controller::Books | index |
| /books/list | MyApp::Controller::Books | list |
'----------------------+--------------------------------------+--------------'
[debug] Loaded Path actions:
.-------------------------------------+--------------------------------------.
| Path | Private |
+-------------------------------------+--------------------------------------+
| /books/list | /books/list |
'-------------------------------------+--------------------------------------'
[info] MyApp powered by Catalyst 5.7000
You can connect to your server at http://localhost.localdomain:3000
Some things you should note in the output above:
=over 4
=item *
Catalyst::Model::DBIC::Schema took our C<MyAppDB::Book> and made it
C<MyApp::Model::MyAppDB::Book> (and similar actions were performed on
C<MyAppDB::Author> and C<MyAppDB::BookAuthor>).
=item *
The "list" action in our Books controller showed up with a path of
C</books/list>.
=back
Point your browser to L<http://localhost:3000> and you should still get
the Catalyst welcome page.
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, with TTSite providing
the formatting for the very simple output we generated in our template.
The count and space-separated list of author last names appear on the
end of each row.
Also notice in the output of the C<script/myapp_server.pl> that DBIC
used the following SQL to retrieve the data:
SELECT me.id, me.title, me.rating FROM books me
Along with a list of the following commands to retrieve the authors for
each book (the lines have been "word wrapped" here to improve
legibility):
SELECT author.id, author.first_name, author.last_name
FROM book_authors me
JOIN authors author ON ( author.id = me.author_id )
WHERE ( me.book_id = ? ): `1'
You should see 5 such lines of debug output as DBIC fetches the author
information for each book.
=head1 AUTHOR
Kennedy Clark, C<hkclark@gmail.com>
Please report any errors, issues or suggestions to the author. The
most recent version of the Catalyst Tutorial can be found at
L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Runtime/lib/Catalyst/Manual/Tutorial/>.
Copyright 2006, Kennedy Clark, under Creative Commons License
(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).