Friday, November 30, 2007

Catalyst for Clueless Beginners

The Catalyst Tutorial is an excellent introduction to Catalyst, but can be hard for total beginners to get into. When I went through the "learning Catalyst" process I tried to make some notes and put together a verrrry simple step-by-step introduction for those of equal cluelessness. I've avoided using a database here because it adds a lot of complexity, and isn't necessary for "Hello, World".

Note: the “$” at the beginning of shell commands is just to indicate a shell prompt. Do not type it.

This tutorial assumes that you are running on a form of Linux. If you are running on Windows, you will have to translate the commands appropriately. The tutorial also assumes basic Perl knowledge. If you don't have that, you should get “Learning Perl” or some other tutorial and learn some Perl first.

Catalyst is a Model/View/Controller framework. You should at least know what that means before you start...

Install and Start Catalyst

Create a Catalyst application:

$ catalyst.pl MyApp

A long list of created directories and files should scroll up your screen.

Change to the MyApp directory that was just created:

$ cd MyApp

Start up the server:

$ script/myapp_server.pl

Go to a browser and look at the url http://localhost:3000

It should display the Catalyst welcome page. If it doesn't, you probably have installation problems. You'll have to get things working before you can do anything else.

Stop the server with a Ctrl-C.

Hello World!

Edit the lib/MyApp/Controller/Root.pm file. You will see the "default" subroutine, which is responsible for displaying the "welcome" screen that you just saw. Later on you'll want to change that to something more reasonable, such as a "404" message, but for now just leave it alone. Add the following subroutine:

sub hello : Global {
  my ( $self, $c) = @_;
  $c->response->body('Hello World!');
}

Notice the "Global term. This is a Catalyst "action" which will make this method one which executes directly after the site URL, or "localhost:3000/hello" at this point. “$c” is the Catalyst “context”, which is used to access Catalyst methods and variables. “response” refers to the Catalyst::Response class, which provides methods for responding to browser requests. “body” is the method which sets the output text to be sent to the browser. This command will send the string “Hello World!” to your browser.

Save the file.

Start the server, open a browser, and go to http://localhost:3000/hello to see "Hello World!"

Hello World using a View and a template

In the Catalyst world, a "View" is not a page of XHTML or a template designed to present page to the browser. It is a Perl module which is more-or-less the equivalent of a config file for the template processor that you have chosen to use. Normally nothing much is in the lib/MyApp/View directory but a couple of Perl modules that set configuration options for Template Toolkit, or Mason, or whatever Perl module(s) you've chosen to handle your presentation (views).

The actual templates are put someplace else, usually underneath the "root" directory. The default, if you do not actually set the directory in a config file, is in the root directory itself: MyApp/root.

The template files are not in the “View” directory, because the lib hierarchy is just for Perl modules. The perl module names are related to their place in the hierarchy. For exammple, MyApp/View/TT.pm is the module MyApp::View::TT.

In order to create the TT config file, run:

$ script/myapp_create.pl view TT TT

This creates the lib/MyApp/View/TT.pm module, which is a subclass of Catalyst::View::TT

Now that the TT.pm "View" exists, Catalyst will automatically use it for displaying the view templates, using the "process" method that it inherits from the Catalyst::View::TT.pm class.

Create a "hello.tt" template file, and put it in the "root" directory (MyApp/root):

[% META title = 'Hello World!' %]
<p>
  This is a TT view template, located in the root directory
<p>

Change the hello method to the following:

sub hello : Global 
{
  my ( $self, $c ) = @_;
  $c->stash->{template} = 'hello.tt';
}

The “stash” is a Perl hash where Catalyst stores variables that will be needed by other components, including templates. Here you are setting a hash element named “template” to “hello.tt”. The Catalyst “renderview” method will automatically take the file you set here, process it with your View module (Template Toolkit), and send it to the browser.

Start up the server and look at http://localhost:3000/hello again. You should see a page saying "Hello World".

Create a simple controller and an action

Create a controller, "Site", for example:

$ script/myapp_create.pl controller Site

In the Site.pm file (in lib/MyApp/Controller) add the following method:

sub test : Local 
{
  my ( $self, $c ) = @_;
  $c->stash->{template} = 'site/test.tt';
}

Notice the "Local" on the method definition. This is necessary for the method to be executed using the "site/test" url. "Global" would put it in the root URL.

Make a subdirectory, "site", in the directory for your template files ("root" by default, "root/src" if you use the TTSite layout), copy the hello.tt file into the site directory as test.tt:

$ cp hello.tt site/test.tt

Edit the test.tt file so you can recognize it...

Bring up the server (or Ctrl-C to bring down and bring it up again), go to http://localhost:3000/site/test

You should see your test.tt file displayed.

Using the TTSite Helper

The TTSite helper will create some useful Template Toolkit templates, including header, footer, wrapper, and other files.

If you want to try it, execute:

$ script/myapp_create.pl view TT TTSite

In addition to the TT.pm file created by "myapp_create.pl view TT TT", TTSite will also create a number of TT templates in the root/src and root/lib directories. It uses the ".tt2" extension, so if you prefer to use the more standard ".tt" extension, you will have to go through and rename the files, and modify the TT.pm file to use that extension.

In addition, it creates a number of template files without extensions: header, footer, wrapper, etc. If you prefer to not have files without file type extensions, you'll have to rename them to header.tt, footer.tt, etc. This also requires changing the references to the files in the templates.

You might want to try TTSite on a sample Catalyst app, examine the template files that it creates, and copy those that you find useful to your own app—unless you're doing a tutorial which counts on having the default TTSite installation. Template Toolkit is a powerful template system with lots of options and a template mini-language. It is well documented at http://template-toolkit.org/

Where does Catalyst look for view templates?

The default directory, if you haven't changed it is "root". If you want to use some other directory name, such as "public", you set the name in MyApp.pm.

For example:

package MyApp::Controller::Test;
sub hello : Local { .. } 

Would by default look for a template in <root>/test/hello. If you set TEMPLATE_EXTENSION in your TT.pm view module to '.tt', it will look for <root>/test/hello.tt

If you are using TTSite defaults, then “included” templates will by in “MyApp/root/lib” and other templates will be in “MyApp/root/src”. It is possible to set both "src" and "include" directories to the same directory. When specifying templates in the stash or including templates in other templates (with the PROCESS, INCLUDE, and INSERT directives) you need to use subdirectories.

For the example above that displays the site/test.tt template, you didn't have to specifically set the template file since it is the default location. You should be able to delete the line setting the template file and Catalyst will still find it, but there will be many times when you will want to specifically set the template to be displayed.

Catalyst Actions

A Catalyst controller contains "actions", subroutines with special attributes, defined using Nicholas Clark's “Attributes” module. These action attributes provide information to the Catalyst dispatcher.

Most URLs look something like:

http://somename.com/site/hello

In this URL, "site" is the controller, and "hello" is an action in the "site" controller.

Actions which are called at the "root" level of the application go in the Root.pm module.

You have seen three different action types: "Private" (on the default subroutine), "Global", and "Local".

A "Global" action is mapped to the application base, with no controller name. A Global "hello" method would be executed with http://somename.com/hello

A "Local" action is executed by prefixing the controller name:

http://somename.com/test/hello

A "Private" action can't be executed directly by URL. It is usually executed by being called by some other action, except for the actions "default", "index", and "auto".

There are a number of additional action types:

Literal Path:  sub bar : Path ('/foo/bar') { }
Regex:         sub bar : Regex ('^item(\d+)/order(\d+)$') { }
               This matches globally, without regard to the controller or namespace.
LocalRegex:    sub bar : LocalRegex('^widget(\d+)$') { }
               This matches only locally.
Chained:       sub bar : Chained : CaptureArgs(1) {} 
     or:       sub bar : Chained('catalog') : Args(1) {}
               See the documentation on DispatchType::Chained
Args:      action type Modifier.
   limits the number of path parts

To start with, most of your Controller actions will probably be "Local". Catalyst is very flexible in how you set up your actions and your URL structure, so you will have lots of choices about how to structure your application and your URLs.

Using the chained type of action can be particularly useful, but isn't advised for your first few Catalyst methods since it's a bit trickier.


--------------------------------------------------------------------------


I hope this has been of some help to someone.

G.Shank

3 comments:

Randal L. Schwartz said...

We did a podcast with one of the Catalyst proponents at http://twit.tv/floss20 - check it out!

Vivek Aanand said...

nice write-up. Can you post more samples on your application? I am getting my feet wet with Catalyst as well.

Agnello Dsouza said...

nice post !!