TITLE

Quick and Dirty Catalyst Tutorial

SYNOPSIS

Catalyst is a framework for building web applications, based around the MVC design pattern - more simply, it provides you with lots of helpful infrastructure, and a suggestion on how to lay out your code, which enables you to rapidly build new applications and reuse other people's code. More simply still: it takes care of most of the boring parts of writing web applications, and allows you to optimize for fun!

The purpose of this tutorial is to teach you enough Catalyst to be dangerous, as quickly as possible. It should take less than an hour to complete. Dangerous, in this case, means "able to make use of the core documentation".

PREREQUISITES

You will need to be familiar with object-oriented Perl, and comfortable with a UNIX-esque command line. You will also need the following to be installed on your target system:

This tutorial assumes that you are developing on the machine you are currently using - if you are not, please substitute the address 127.0.0.1 in the text for the hostname of the machine on which you are running the examples.

EXAMPLE: Hello Counting Machine

The first app we're going to build is a simple XMLRPC-based 'adding machine'. XMLRPC is a very simple HTTP-based remote procedure-call language, but we'll avoid handling it directly.

Find a directory you want to develop in, and cd to it. We create the stubs for our Catalyst app by typing:

 catalyst.pl Tutorial; cd Tutorial;

Expect a whole lot of output. We now (hopefully) have a working Catalyst application. Try and run it with:

 ./script/tutorial_server.pl

Connect to http://127.0.0.1:3000, and you should see the Catalyst "Welcome Page". Pretty as that is, it's not a great deal of fun. So let's set up our XMLRPC interface. This is easy:

Open up ./lib/Tutorial.pm, and change:

 use Catalyst qw/-Debug Static::Simple/;

to:

 use Catalyst qw/-Debug Static::Simple XMLRPC/;

Catalyst adds the plugin directory to the path to search for libraries, so XMLRPC is sufficient to find and load Catalyst::Plugin::XMLRPC. Now run the command:

 ./script/tutorial_create.pl controller XMLRPC

This creates a new Controller module called XMLRPC. More on this shortly, but in this case, the end result is that requests for the URL '/xmlrpc' against your server will be handled by the module we just created.

Find it and open it at ./lib/Tutorial/Controller/XMLRPC.pm. Add the following two subs:

    sub default : Private {
        my ( $self, $c ) = @_;
        $c->xmlrpc;
    }

    sub add : Remote {
        my ( $self, $c, $a, $b ) = @_;
        return $a + $b;
    }

Save, exit, and restart your server:

 ./script/tutorial_server.pl

We should now have a severely limited calculator. SOAP::Lite has provided us with 'XMLRPCsh.pl', so we'll use that to connect to our 'program that calculates things':

    % XMLRPCsh.pl http://127.0.0.1:3000/xmlrpc
    
    Usage: method[(parameters)]
    > add( 1, 2 )
    --- XMLRPC RESULT ---
    '3'

Exercise

Add a method to accept a word and a number via XMLRPC, and return a string with that word printed $number times.

CONTROLLERS

We just created a Controller. Catalyst is an MVC framework, where MVC stands for Model (data source), View (output filters), and Controller (application logic).

Let's take a look at the add subroutine we added earlier:

    sub add : Remote {

What's ": Remote" doing? It's an attribute. Attributes are used in Controllers to turns subroutines in to actions. When we're using the XMLRPC plugin, we used 'Remote' as the attribute to say we want these methods to be publicly available, as you probably realised from the Exercise in the last chapter.

Actions are subroutines with attributes that live in a Controller. In general, these actions map directly to URLs you would request. 'Remote' isn't a useful one to remember as it's not really used elsewhere in Catalyst outside of the XMLRPC plugin, AND it doesn't match a URL. Sorry! We will get on to other attributes a little later, but first let's take a look at 'Private'.

'Private' is the other type of attribute that matches no URL. There are several 'built-in' types of 'Private' actions such as 'default', which is called when no other action matches the URL called.

Let's take a quick look at what happened when we made out request for http://127.0.0.1:3000/xmlrpc. Our request for /xmlrpc meant that Catalyst looked for a Tutorial::Controller::XMLRPC (the mechanism is case-insensitive), and as there was no specific action in that module for our URL, the default action was called.

Our 'default' routine, however, does some magic to do with the XMLRPC plugin, so at this point, our example ceases to be of any further use for a while.

EXAMPLE: Climate Search (Version 1)

We're going to add a Model now. A Model is a data source. Our first step is to get Catalyst to create the stubs of this Model for us:

 ./script/tutorial_create.pl model Factbook

Open up lib/Tutorial/Model/Factbook.pm, and under the use base command, stick:

 use WebService::CIA;
 use WebService::CIA::Source::Web;

 my $source = WebService::CIA::Source::Web->new();
 my $cia = WebService::CIA->new({ Source => $source });

 sub lookup {
 
        my ( $class, $country ) = @_;
        
 # Country is a two-letter country code.
        
        return $cia->get( $country, "Climate");
 
 }

Now open ./lib/Tutorial/Controller/XMLRPC.pm and add the subroutine:

  sub climate : Remote {

    my ( $self, $c, $country ) = @_;

    my $climate = $c->model( 'Factbook' )->lookup( $country );

    return $climate;

  }

Let's start the server, and have some fun finding out about the climate of different countries:

 > climate('uk')
 --- XMLRPC RESULT ---
 'temperate; moderated by prevailing southwest winds over
 the North Atlantic Current; more than one-half of the days
 are overcast'

 > climate('no')
 --- XMLRPC RESULT ---
 'temperate along coast, modified by North Atlantic Current;
 colder interior with increased precipitation and colder
 summers; rainy year-round on west coast'

 > climate('sp')
 --- XMLRPC RESULT ---
 'temperate; clear, hot summers in interior, more moderate
 and cloudy along  coast; cloudy, cold winters in interior,
 partly cloudy and cool along coast'

Exercise

Create a 'description' method which returns the climate data AND the full-name of the country (WebService::CIA::get uses 'Country name' for this piece of data).

MODELS

There's very little magic going on with Models, and in fact, you can just call outside modules from your controller instead of using a Model for simple things.

There are a couple of advantages to having a Model that uses 'Catalyst::Model' as its base, and lives in the Catalyst module hierarchy - the one we'll mention here is it will get automatically loaded when you call:

 $c->model( 'ModelName' );

You can easily create Models that wrap existing modules - simply create a module with the contents:

 package Tutorial::Model::FooBar;
 use base qw/Catalyst::Base Your::Module::Goes::Here/;
 1;

EXAMPLE: Climate Search (Version 2)

XMLRPC isn't much fun though. We want to build websites! While we could create a new Controller at this point, presumably the WWW interface should be the default one, so we'll add to the default Controller.

The default controller is in fact ./lib/Tutorial.pm, so that's where we'll add our action (avoid putting non-private actions in the main application class). Open up ./lib/Tutorial.pm, and change the existing default sub to be:

 sub default : Private {
    
    my ( $self, $c ) = @_;

    my $country = $c->req->param('country') || 'uk';
    
    my $climate = $c->model( 'Factbook' )->lookup( $country );
        
    if ( $climate ) {

      $c->res->body( "Climate in $country: \n\n $climate" );
  
    } else {

      $c->res->body( "Country $country not found!" );
  
    }

 }

Save, exit, restart your server, and visit the following URLs in your web-browser:

http://127.0.0.1:3000/

http://127.0.0.1:3000/?country=us

http://127.0.0.1:3000/?country=zz

CONTEXT

All our actions so far have started with a variation of:

 my ( $self, $c ) = @_;

$self will be familiar to anyone who's worked with Perl object-orientated system before. But what's $c? $c is the Context object, and contains several useful things, two of which we've seen and will talk about here.

Catalyst::Request

 $c->request
 $c->req # alias

The request object contains all kinds of request-specific information, like query parameters, cookies, uploads, headers, and more.

 $c->req->params->{foo};
 $c->req->cookies->{sessionid};
 $c->req->headers->content_type;
 $c->req->base;

Catalyst::Response

 $c->response
 $c->res # alias

The response is like the request, but contains just response-specific information.

 $c->res->body('Hello World');
 $c->res->status(404);
 $c->res->redirect('http://www.pjls.co.uk/');

EXAMPLE: Climate Search (Version 3)

Manually creating our URLs and outputting plain-text is far too 1991 for my liking. So let's create an HTML front-end - we're going to use Template Toolkit to do this.

Template Toolkit is generally the favoured View module in Catalyst applications, so is pretty easy to get running. Let's start the ball rolling with:

 ./script/tutorial_create.pl view TToolkit TT

The trailing 'TT' here is a reference to a Helper module: Catalyst::Helper::View::TT as it happens. That's outside the scope of this tutorial, so don't think about it too hard.

You may or may not know anything about Template Toolkit. Either way, you're going to have to tell our View where to find the template files we're going to use.

So open up ./lib/Tutorial/View/TToolkit.pm and add:

 __PACKAGE__->config({
   'INCLUDE_PATH' => Tutorial->path_to('templates')
 });

under use base 'Catalyst::View::TT'

Save, exit, and type:

 mkdir templates;

Bingo! Now copy and paste the following in to ./templates/index.tt2:

 <html><head><title>Country Info</title></head><body>
 
 <form method = "get">Country Code: <input type = "text" name = "country"
 value = "[% country %]" /><input type = "submit" value = "Look Up" />
 </form><hr />
 
 [% IF climate %]<h1>Data for [% country %]</h1>[% climate %]
 [% ELSE %]Country Not Found![% END %]<hr />

So far so good! Now we need to edit our action to use this View. Open up ./lib/Tutorial.pm, and change the default action to be:

 sub default : Private {

    my ( $self, $c ) = @_;

  # Retrieve the country, and make sure we just save two characters 

    my $country = $c->req->param('country') || '';
    $country =~ s/(..).*/$1/; 
    $country = 'uk' unless $country;
      
  # Save all the data we need for the template

    $c->stash->{'climate'} = $c->model( 'Factbook' )->lookup( $country );
    $c->stash->{'country'} = $country;
    
    $c->stash->{'template'} = 'index.tt2';  
    
  # Call the view

    $c->forward( 'Tutorial::View::TToolkit' );  
  
 } 

Save, and restart the server, and point your browser at it.

Stash and path_to

So what's the stash? It's somewhere you can put stuff. Anything in fact. It just so happens that Catalyst::View::TT reads the template name from it.

You'll have also noticed our sneaky use of <Tutorial-path_to>>. path_to is a method that Tutorial has inherited from Catalyst, and it gives the path to the base directory of our application.

HEY YOU!

Enjoyed this? Buy me a book. Thanks :-)

SEE ALSO

The best resource to get your Catalyst skills up to scratch, and to see what else is on offer, is probably the 2005 Catalyst Advent Calendar (http://catalyst.perl.org/calendar/2005/) in conjunction with the Catalyst::Manual.

If you're looking for CRUD functionality (i.e. you want a database editor), have a look at Catalyst::Enzyme.

AUTHOR

Peter Sergeant / PJL Systems Ltd / pete at clueball dot com

Copyright 2006

Pickup and Seduction articles

Adam Lyons (Lions) and The Rules of Seduction (Channel 4)