Day 1 - Getting started with Catalyst and Subversion

Getting Started with Catalyst and Version Control

Getting Started

Catalyst ships with all the tools you need to get started with web application programming. It uses the Model-View-Controller (MVC) paradigm to separate the application logic, data storage, and display logic from one another. This is a standard pattern, taken from the Smalltalk programming language and popularised by the Gang of 4 book. MVC coding is an excellent antidote to "spaghetti code". Catalyst's use of this approach, its powerful dispatcher (controller logic), and its model and view agnosticism is what accounts for its power.

For the first day of Catalyst Advent 2006, I'll keep it traditional: we'll write a complete, if basic, sample application that shows several aspects of Catalyst. Our application, called Cheer, will display the number of days remaining until Christmas - how seasonally appropriate! I'll also show the use of version control, which is not usually demonstrated in such examples, but which is crucial in the development process for any serious application.

As a result of the MVC architecture, a typical Catalyst application has a well-defined structure, and contains quite a lot of files. This makes using version control from the start very important. For this example I'll use Subversion, a powerful version-control system that grew out of CVS (which is in extremely wide use, but has many limitations). Many Catalyst developers use svk, itself based on Subversion.

The example application here is available from the Catalyst subversion repository. Instructions for obtaining it are given at the end of this article.

  • First create a new Subversion repository to put your code in

     $ svnadmin create /usr/local/src/xmas
    
    
  • Then we want to put some files in there:

     $ cd /tmp
     $ mkdir xmas 
     $ cd xmas
     $ mkdir tags trunk branches
     $ svn import file:///usr/local/src/xmas -m 'initial import'
    
    
  • Next up is to check out a working copy:

     $ svn co file:///usr/local/src/xmas/trunk xmas 
     $ cd xmas
    
    
  • and we're ready to create and check in our skeleton application:

     $ catalyst.pl Cheer
     [output snipped]
     $ svn add Cheer
     $ svn ci Cheer -m 'initial import of skeleton Catalyst application'
     $ cd Cheer # this is where we'll be for the rest of this calendar entry
    
    

And we have a version-controlled skeleton application all ready for use.

The purpose of the trunk tags and branches directories are beyond the scope of this tutorial, however we are working exclusively out of the trunk of the repository. The branches directory is for separate lines of development, for example branches can be used for addition of substantial new features, or significant refactoring. Once the branch is complete it can be merged back to trunk. The tags directory should contain project snapshots.

Modify the Root Controller to have More Sensible Default Behavior

By default catalyst.pl makes the default action of the root controller a "hello world" screen. We're going to change this so that the index action provides the "hello world" screen and the default action returns a 404 not found error code.

I'm going to do this to my local copy, then provide a svn diff which you can use to patch your local copy. Back in a sec.

OK, that took me a couple of minutes. First I check that my code actually compiles:

 $ script/cheer_server.pl
 [output snipped]
 You can connect to your server at http://localhost:3000
 [control-c]
 $ svn diff
 Index: lib/Cheer/Controller/Root.pm
 ===================================================================
 --- lib/Cheer/Controller/Root.pm        (revision 2)
 +++ lib/Cheer/Controller/Root.pm        (working copy)
 @@ -28,9 +28,17 @@

  sub default : Private {
      my ( $self, $c ) = @_;
 +    $c->res->status(404);
 +    $c->response->body( '404 not found' );
 +}

 -    # Hello World
 -    $c->response->body( $c->welcome_message );
 +=head2 index
 +
 +=cut
 +
 +sub index : Private {
 +    my ($self, $c) = @_;
 +    $c->res->body('Hello world!');
  }

  =head2 end

You can copy and paste the above code (from the line below "svn diff" above to "=head2 end") into a file - cheer.patch - I'm going to put it in /tmp/, but I need to undo my local modifications first. Again I'm in the root dir for my application:

 $ svn revert lib/Cheer/Controller/Root.pm

and patch the source from my patch at /tmp/cheer.patch. Make sure that if you're following along at home that you get rid of all leading spaces from the patch. The following Perl one-liner will do that (followed by the invocation to patch):

 $ perl -p -i -e 's/^\s+//' /tmp/cheer.patch
 $ patch -p0 < /tmp/cheer.patch

Now I can commit the changes:

 $ svn ci -m 'Updated root controller to give more sensible default behavior'

Note that I don't have to specify a file or directory if I want all changes below my current directory to be committed to the repository in this change set.

Adding a View

We're going to use the Template Toolkit (TT) view for this example. So we issue the following command from the application root (in my case ~/Cheer):

 $ script/cheer_create view TT TT

this creates a couple of new files which we can add to the version control repository in the following way:

 $ find . -name '*TT.*' | xargs svn add
 $ svn ci -m 'created skeleton view'

Often, this is all you need to create the view. The only thing you have to do to create your own view (from your own templating system, for example) is to provide a process method in your application, as documented in Catalyst::View (although a render method is also rather useful). There are quite a lot of other views available for catalyst, but the Template Toolkit is by far the most popular. HTML::Mason the next most popular option.

I'm going to update the root controller to use the Template Toolkit now. Here's the patch which includes changes to the controller code and the addition of a couple of templates:

 Index: root/404.tt
 ===================================================================
 --- root/404.tt	(revision 0)
 +++ root/404.tt	(revision 0)
 @@ -0,0 +1,3 @@
 +<html><head><title>404 error!</title></head>
 +<body>Error:  [% c.req.path %] not found</body>
 +</html>
 Index: root/hi_there.tt
 ===================================================================
 --- root/hi_there.tt	(revision 0)
 +++ root/hi_there.tt	(revision 0)
 @@ -0,0 +1,3 @@
 +<html><head><title>Happy winter solstice</title></head>
 +<body>And best regards for the new year!</body>
 +</html>
 Index: lib/Cheer/Controller/Root.pm
 ===================================================================
 --- lib/Cheer/Controller/Root.pm	(revision 3)
 +++ lib/Cheer/Controller/Root.pm	(working copy)
 @@ -29,7 +29,7 @@
  sub default : Private {
      my ( $self, $c ) = @_;
      $c->res->status(404);
 -    $c->response->body( '404 not found' );
 +    $c->stash->{template}='404.tt';
  }

  =head2 index
 @@ -38,7 +38,7 @@

  sub index : Private {
      my ($self, $c) = @_;
 -    $c->res->body('Hello world!');
 +    $c->stash->{template} = 'hi_there.tt';
  }

  =head2 end




Because I added a couple of files to the application, this time I had to add them to my working copy before creating the patch. This is what I did (from the application root):

 $ svn add root/*tt
 $ svn diff > /tmp/tt.patch

This time it's up to you to figure out how to apply it - remember to strip leading spaces first.

A (very) Simple Model.

As I said at the beginning, Catalyst is agnostic to the model and view you use. This gives us an opportunity to show you a very simple example. Most commonly the model will be used to talk to some kind of a database, and Catalyst developers usually favor DBIx::Class as their database mapping layer. However, here we're going to grab data from the system clock instead.

First, create a new model called "Now" (from the application root), and commit it to the Subversion repository.

 $ script/cheer_create.pl model Now
 $ find . -name '*Now.*' | xargs svn add
 $ svn ci -m 'added skeleton Now model'

I'm going to go and update the model to tell us the number of days to Christmas in the index action in the root controller. Again I'll provide this as a patch. We're going to use the Date::Calc module from CPAN this time, so I'll also update the Makefile.PL so that you're automatically told about dependencies from CPAN when you run 'perl Makefile.PL' from the application root. Some applications also have actions defined for make installdeps so that if you run perl Makefile.PL and then make installdeps cpan is started and all the missing dependencies are installed. However, this is not done automatically (yet?).

Here's the patch:

 Index: root/hi_there.tt
 ===================================================================
 --- root/hi_there.tt	(revision 4)
 +++ root/hi_there.tt	(working copy)
 @@ -1,3 +1,4 @@
  <html><head><title>Happy winter solstice</title></head>
 -<body>And best regards for the new year!</body>
 +<body>And best regards for the new year!  There are [% c.stash.days_till_xmas %]
 +days left until Santa comes.  </body>
  </html>
 Index: lib/Cheer/Model/Now.pm
 ===================================================================
 --- lib/Cheer/Model/Now.pm	(revision 5)
 +++ lib/Cheer/Model/Now.pm	(working copy)
 @@ -3,7 +3,9 @@
  use strict;
  use warnings;
  use base 'Catalyst::Model';
 +use Date::Calc qw(:all);

 +
  =head1 NAME

  Cheer::Model::Now - Catalyst Model
 @@ -12,6 +14,21 @@

  Catalyst Model.

 +=head2 days_till_xmas
 +
 +Tells us how many days there are until Christmas
 +
 +=cut
 +
 +sub days_till_xmas {
 +    my ($self) = @_;
 +    my ($year, $month, $day) = Today();
 +    my ($xmas_day, $xmas_month) = qw/25 12/;
 +    my $days_till_xmas =
 +        Delta_Days($year, $month, $day, $year, $xmas_month, $xmas_day);
 +    return $days_till_xmas;
 +}
 +
  =head1 AUTHOR

  Kieren Diment
 Index: lib/Cheer/Controller/Root.pm
 ===================================================================
 --- lib/Cheer/Controller/Root.pm	(revision 4)
 +++ lib/Cheer/Controller/Root.pm	(working copy)
 @@ -38,6 +38,7 @@

  sub index : Private {
      my ($self, $c) = @_;
 +    $c->stash->{days_till_xmas} = $c->model('Now')->days_till_xmas();
      $c->stash->{template} = 'hi_there.tt';
  }

 Index: Makefile.PL
 ===================================================================
 --- Makefile.PL	(revision 2)
 +++ Makefile.PL	(working copy)
 @@ -7,6 +7,7 @@
  requires 'Catalyst::Plugin::ConfigLoader';
  requires 'Catalyst::Plugin::Static::Simple';
  requires 'Catalyst::Action::RenderView';
 +requires 'Date::Calc';
  requires 'YAML'; # This should reflect the config file format you've chosen
                   # See Catalyst::Plugin::ConfigLoader for supported formats
  catalyst;




Wrap up.

So I've shown you how to use Catalyst with Subversion, touched briefly on the View, and showed you how to use a custom model that uses a non-catalyst CPAN module. If you don't know Catalyst, we recommend that you go through the tutorials from Catalyst::Manual::Tutorial which go into quite a lot more detail. For more details on Subversion, and particularly the function of the tags, trunk, and branches directory structure which we created, see the excellent free Subversion Book.

You can obtain the code from this Advent Calendar entry from the Catalyst Subversion repository by issuing the following command:

 $ svn co http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Cheer

AUTHOR

Kieren Diment <diment@gmail.com>