DBIx::Class and Catalyst

How to quickly generate a database backed web application


Wellington Perl Mongers July 2006

Finlay Thompson

What is DBIx::Class?

What is Catalyst?

A Single Table Application

Catalyst Scaffolding

Catalyst comes with a script for building the standard modules and directory structures. This is an excellent way to get started

bash $ catalyst.pl Sample::App
created "Sample-App"
created "Sample-App/script"
created "Sample-App/lib"
created "Sample-App/root"
created "Sample-App/root/static"
created "Sample-App/root/static/images"
created "Sample-App/t"
created "Sample-App/lib/Sample/App"
.....

Create Database

Create database. In this example we use PostgreSQL, but other databases are similar.

sql/schema-100.sql
CREATE SEQUENCE person_seq;
CREATE TABLE person (
    person_id  INT  NOT NULL DEFAULT nextval('person_seq'),
    firstname  TEXT NOT NULL,
    lastname   TEXT NOT NULL,
    nickname   TEXT NOT NULL
);
ALTER TABLE person ADD 
    CONSTRAINT person_pk
    PRIMARY KEY (person_id);
10 lines

Setup DBIx::Classes

lib/Sample/DB.pm
package Sample::DB;
use base qw/DBIx::Class::Schema/;
__PACKAGE__->load_classes(qw/ Person /);
1;
4 lines (14 total) lib/Sample/DB/Person.pm
package Sample::DB::Person;
use base 'DBIx::Class';
__PACKAGE__->load_components(qw/ PK::Auto::Pg Core /);
__PACKAGE__->table('person');
__PACKAGE__->add_columns(qw/ person_id firstname lastname nickname /);
__PACKAGE__->set_primary_key('person_id');
1;
7 lines (21 total)

DBIx::Class test

Oops we forgot to create the test!

t/05dbic.t
use strict;
use warnings;
use Test::More tests => 2;
use lib 'lib';
BEGIN { 
    use_ok 'Sample::DB';
    use_ok 'Sample::DB::Person';
};

Create a new Controller

Catalyst provides some tools for creating new Controllers, Models and Views.

This little script creates a new controller module and corresponding test.

bash $ script/sample_app_create.pl controller Person
 exists "lib/Sample/App/Controller"
 exists "t"
created "lib/Sample/App/Controller/Person.pm"
created "t/controller_Person.t"

The New Controller

The new controller class does not contain any methods yet.

lib/Sample/App/Controller/Person.pm
package Sample::App::Controller::Person;
use strict;
use warnings;
use base 'Catalyst::Controller';
1;
4 lines (25 total)

Create a new View class

For a change we use the Petal template system. This borrows a Zope style XHTML syntax. You can use Mason, Template Toolkit or even Seamstress

bash $ script/sample_app_create.pl view Petal Petal
 exists "lib/Sample/App/View"
 exists "t"
created "lib/Sample/App/View/Petal.pm
created "t/view_Petal.t"

The New View

Again the new view class is very small!

lib/Sample/App/View/Petal.pm
package Sample::App::View::Petal;
use strict;
use base 'Catalyst::View::Petal';
1;
4 lines (29 total)

Use it in the Person Controller

We create our first action in the Person controller to use the Petal template system.

lib/Sample/App/Controller/Person.pm
sub end : Private {
    my ($self, $c) = @_;
    if ($c->stash->{template}) {
        $c->forward('Sample::App::View::Petal');
    }
    else {
        $c->res->redirect( '/person/list' );
    }
}
9 lines (38 total)

Better make the list method too

List all the objects in the database

lib/Sample/App/Controller/Person.pm
sub list : Local {
    my ($self, $c) = @_;
    my $people = [$c->model('Schema::Person')
                    ->search()
                    ->all];
    $c->stash->{people} = $people || [];
    $c->stash->{add_link}  = "location='/person/add';";
    $_->{edit_link} = "location='/person/edit?id=".$_->person_id."';" for @$people;
    $c->stash->{template} = 'person/list.html';
}
10 lines (48 total)

An Edit Screen

We do this in two steps, first displaying the edit form, and then updating the database

lib/Sample/App/Controller/Person.pm
sub edit : Local {
    my ($self, $c) = @_;
    my $person_id = $c->req->param('id');
    my $person = $c->model('Schema::Person')
                   ->find({ person_id => $person_id });
    $c->stash->{person} = $person || {};
    $c->stash->{template} = 'person/edit.html';
}
sub do_edit : Local {
    my ($self, $c) = @_;
    my $data = $c->req->params();
    my $person = $c->model('Schema::Person')
                   ->find({ person_id => $data->{person_id} });
    $c->model('Schema')->schema->txn_do(sub {
        $person->update($data);
    }, $data);
}
18 lines (66 total)

Final Total