• If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • You already know Dokkio is an AI-powered assistant to organize & manage your digital files & messages. Very soon, Dokkio will support Outlook as well as One Drive. Check it out today!

View
 

Todos 06-Building with Perl, Catalyst Framework and DBIx::Class

Page history last edited by Pavel Karoukin 13 years, 8 months ago

Todos 06 - Building with Perl, Catalyst Framework and DBIx::Class

 

About Perl

 

Perl - is a highly capable, feature-rich programming language with over 22 years of development. Perl 5 runs on over 100 platforms from portables to mainframes. Perl is suitable for both rapid prototyping and large scale development projects.

 

About Catalyst Framework

 

Catalyst Framework - a very flexible web application framework which acts as a glue between a lot of already existing modules on CPAN. Main principle - DRY - Don't Repeat Yourself. The version I am using in this tutorial is 5.80. 

 

 

About DBIx::Class

 

DBIx::Class is a flexible and extensible Object Relation Mapper (ORM). It supports a lot of different RDBMS out of the box. In this tutorial we will be using the most lightweight option - Sqlite3

 

Goals

 

I want to show how easy it could be to build REST back-end for Todos SproutCore tutorial application with Catalyst Framework and all CPAN's power behind it.

 

 

Initial setup

 

I will start with a new Catalyst application. If you plan to add this functionality to an already existing Catalyst application, go to step "SproutCore proxy modifications"

 

Before you can bootstrap a new Catalyst application, you need to have following CPAN modules:

1) Catalyst::Devel

2) Catalyst::Controller:REST

3) Catalyst::View::JSON

4) DBIx::Class

5) DBIx::Class::Schema

 

If you haven't installed them yet, it can be done with:

 

In Ubuntu:

 

$ sudo apt-get install libcatalyst-perl libcatalyst-modules-perl libcatalyst-modules-extra-perl \
> libcatalyst-action-rest-perl libdbix-class-perl libdbix-class-schema-loader-perl

 

In other Linux-based OSes (it will take a while =)):

 

$ cpan Catalyst::Devel
$ cpan Catalyst::Controller::REST
$ cpan Catalyst::View::JSON
$ cpan DBIx::Class
$ cpan DBIx::Class::Schema

 

 

After you got all required dependencies, issue the following command in your projects folder:

 

$ catalyst.pl Todos

 

Here is the output:

 

created "Todos"
created "Todos/script"
created "Todos/lib"
created "Todos/root"
created "Todos/root/static"
created "Todos/root/static/images"
created "Todos/t"
created "Todos/lib/Todos"
created "Todos/lib/Todos/Model"
created "Todos/lib/Todos/View"
created "Todos/lib/Todos/Controller"
created "Todos/todos.conf"
created "Todos/lib/Todos.pm"
created "Todos/lib/Todos/Controller/Root.pm"
created "Todos/README"
created "Todos/Changes"
created "Todos/t/01app.t"
created "Todos/t/02pod.t"
created "Todos/t/03podcoverage.t"
created "Todos/root/static/images/catalyst_logo.png"
created "Todos/root/static/images/btn_120x50_built.png"
created "Todos/root/static/images/btn_120x50_built_shadow.png"
created "Todos/root/static/images/btn_120x50_powered.png"
created "Todos/root/static/images/btn_120x50_powered_shadow.png"
created "Todos/root/static/images/btn_88x31_built.png"
created "Todos/root/static/images/btn_88x31_built_shadow.png"
created "Todos/root/static/images/btn_88x31_powered.png"
created "Todos/root/static/images/btn_88x31_powered_shadow.png"
created "Todos/root/favicon.ico"
created "Todos/Makefile.PL"
created "Todos/script/todos_cgi.pl"
created "Todos/script/todos_fastcgi.pl"
created "Todos/script/todos_server.pl"
created "Todos/script/todos_test.pl"
created "Todos/script/todos_create.pl"
Change to application directory and Run "perl Makefile.PL" to make sure your install is complete

 

 

At this point you will get a bare-bone Catalyst application. You can start it by:

 

$ cd Todos/
$ ./script/todos_server.pl

 

And you will be able to access it at http://localhost:3000/

 

 

SproutCore proxy modifications

 

You will need to tell 'sc-server' to proxy all '/tasks' requests to our new Catalyst application at localhost:3000. To do this just add following line to 'Buildfile' from your SproutCore Todos application:

 

    proxy '/tasks', :to => 'localhost:3000'

 

To be sure 'sc-server' will catch up all these changes - stop 'sc-server', remove './tmp' application's folder and start 'sc-server' again.

 

 

Database

 

I will show you how to use SQLite3 database here, but if you need to go 'big' later - it's pretty easy to switch RDBMS as long as you do not use any specific features of some specific database. To initialize a new SQLite3 DB issue following commands inside Catalyst application folder:

 

$ sqlite3 tasks.db
SQLite version 3.6.22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> CREATE TABLE Task (
   ...>  id integer not null primary key autoincrement,
   ...>  isdone tinyint default 0,
   ...>  description text default '');
sqlite> INSERT INTO Task (isdone,description) VALUES
   ...> (1, 'Create database.');
sqlite> INSERT INTO Task (isdone,description) VALUES
   ...> (0, 'Create model.');
sqlite> INSERT INTO Task (isdone,description) VALUES
   ...> (0, 'Create controller.');
sqlite> INSERT INTO Task (isdone,description) VALUES
   ...> (0, 'Create view.');
sqlite> .q

 

At this point we have a new SQLite3 database called tasks.db and with one table - Task

 

 

Catalyst Model

 

Now we need to create a model which will allow us access our SQLite3 database through ORM module DBIx::Class. We have already installed module to dump DB schema and create Perl modules to use as module. To do it issue the following command inside Catalyst application root folder:

 

$ ./script/todos_create.pl model TaskDB DBIC::Schema Todos::Schema \
> create=static dbi:SQLite:tasks.db

 

Here is a sample output:

 

exists "/home/pavel/projects/catalyst/Todos/script/../lib/Todos/Model"
exists "/home/pavel/projects/catalyst/Todos/script/../t"
Dumping manual schema for Todos::Schema to directory /home/pavel/projects/catalyst/Todos/script/../lib ...
Schema dump completed.
created "/home/pavel/projects/catalyst/Todos/script/../lib/Todos/Model/TaskDB.pm"
created "/home/pavel/projects/catalyst/Todos/script/../t/model_TaskDB.t"

 

Now you have your DB model in lib/Todos/Model/TaskDB.pm

 

 

JSON View

 

In Catalyst Framework it's easy to have multiple views for your application. For example, you can have HTML view, JSON view and CSV view served with one application and model. In our example I will show how to add JSON View. Later you can add any other types of view yourself. To create new View just issue the following command inside your Catalyst application root folder:

 

$ ./script/todos_create.pl view JSON JSON

 

Here is a sample output:

 

exists "/home/pavel/projects/catalyst/Todos/script/../lib/Todos/View"
exists "/home/pavel/projects/catalyst/Todos/script/../t"
created "/home/pavel/projects/catalyst/Todos/script/../lib/Todos/View/JSON.pm"
created "/home/pavel/projects/catalyst/Todos/script/../t/view_JSON.t"

 

Now you have your own JSON view in your application.

 

 

REST Controller

 

So far we didn't write any line of the code - everything works out of the box. Now we need to create controller which will "glue" everything together and will bring our application to life. Create a new controller by issuing the following command inside of Catalyst application root folder:

 

$ ./script/todos_create.pl controller Tasks

 

Here is a sample output:

 

exists "/home/pavel/projects/catalyst/Todos/script/../lib/Todos/Controller"
exists "/home/pavel/projects/catalyst/Todos/script/../t"
created "/home/pavel/projects/catalyst/Todos/script/../lib/Todos/Controller/Tasks.pm"
created "/home/pavel/projects/catalyst/Todos/script/../t/controller_Tasks.t"

 

At this point we have a new controller Tasks at './lib/Todos/Controller/Tasks.pm'. Now we come to actual coding - open this file in a preferred editor.

 

First, we need to tell perl to use Catalyst::Controller:REST as the base for our new controller. 

 

To do this replace:

BEGIN { extends 'Catalyst::Controller' }

 

With:

BEGIN { extends 'Catalyst::Controller::REST' }

 

This parent class allows to get access to submitted JSON and create separate actions for different REST operations (GET/POST/PUT/DELETE). Any non implemented REST operation will return '405 Non implemented' error by default.

 

Replace default 'index' action subroutine with:

sub index :Path Args(0) :ActionClass('REST') { }

 

And add another action:

sub task :Path('/tasks') :Args(1) :ActionClass('REST') { }

 

 

This will allow us to define operation-specific actions later. Basically, this tells Catalyst to route all '/tasks' requests to 'sub index()' and all '/tasks/xxx' requests to 'sub task()'. ':ActionClass('REST')' tells Catalyst to look for operation-specific subroutines instead of using default one. More on operation-specific routines below.

 

 

Let's start from the end()

 

First thing I want to do in the new controller - define 'end()' action. This action will always be fired after executing all matched actions. In our example I want to use it to forward execution to our JSON View. To do this add the following code to the end of the Tasks.pm controller:

 

sub end :Private {
  my ($self, $c) = @_;
 
  $c->forward("View::JSON");
}

 

This will convert anything we put into $c->stash into nice JSON-formatted output to be consumed by your SproutCore application.

 

Technically, it's not required to implement this action, since right now our application has only one view - JSON. By default Catalyst will run 'end()' action of 'Root.pm' controller if no 'end()' action defined in current one. But if you decide to add new views later, like to display output in plain HTML, we need specifically point Catalyst to render stash in JSON format.

 

 

Implement '404 Page not found' 

 

Just to have peace of mind let's implement '404 Page not found' action as well. During runtime if request didn't produce any matches to existing actions, Catalyst will fire 'default' action instead. 

 

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

 

This code sets 404 HTTP result code and return 'Page not found' in result.

 

 

REST GET - List all tasks

 

First action we need to create - list of all current tasks entries. Insert the following code into your Tasks.pm controller after 'index' action definition:

 

sub index_GET {
    my ( $self, $c ) = @_;
    my @tasks;
 
    foreach my $task ($c->model("TaskDB::Task")->all) {
      push @tasks, {
        'isDone' => $task->isdone,
        'description' => $task->description,
        'guid' => '/tasks/'. $task->id,
      };
    }
 
    $c->stash->{content} = \@tasks;
}

 

As you can see, 'index' action name was suffixed with '_GET' - this tells Catalyst that this subroutine will be used for GET '/tasks' requests. Basically, this subroutine fetches all entries from Model and puts it into $c->stash to be shown as JSON later with the help of JSON view.

 

 

REST POST - Create new task

 

Similarly to GET action create new POST action:

 

sub index_POST {
    my ( $self, $c ) = @_;
 
    my $task = $c->model("TaskDB::Task")->create({
      "isdone" => $c->req->data->{isDone},
      "description" => $c->req->data->{description},
    });
 
    $c->res->redirect('/tasks/'. $task->id);
}

 

This action creates new row in DB and returns a redirect response to the new record. As you can see Catalyst::Controller::REST already parsed JSON into $c->req->data for us. So we do not need to bother with decoding JSON at all. 

 

 

REST GET - Fetch individual Task record

 

I didn't find where this should be used, but since it's easy to implent, here we go:

 

sub task_GET {
    my ( $self, $c, $id ) = @_;
 
    my $task = $c->model("TaskDB::Task")->find($id);
 
    if (!$task) {
      $c->detach('default');
    }
    $c->stash->{content} = {
      'guid' => '/tasks/'. $task->id,
      'isDone' => $task->isdone,
      'description' => $task->description,
    };
}
 

 

REST PUT - Update Task record

 

It's very easy to update a row in DB - just find the requested row, assign new values and issue 'update()' on it:

 

sub task_PUT {
    my ( $self, $c, $id ) = @_;
 
    my $task = $c->model("TaskDB::Task")->find($id);
 
    if (!$task) {
      $c->detach('default');
    }
    $task->description($c->req->data->{description});
    $task->isdone($c->req->data->{isDone});
    $task->update();
}

 

 

REST DELETE - Delete Task record

 

Probably, shortest action:

 

sub task_DELETE {
    my ( $self, $c, $id ) = @_;
    my $task = $c->model("TaskDB::Task")->find($id)->delete;
}
 

Find and delete. That's it.

 

 

Final

 

At this point, if you or I haven't made any mistypes, application should be run by issuing the following command in Catalyst application root folder:

 

$ ./script/todos_server.pl

 

If it runs fine and doesn't produce any errors - you should be able to connect to it from your SproutCore Todos tutorial application if you have already done 'Step 7: Hooking Up to the Back-end'. 

 

If you encounter some errors - it should print all of them right into console. Go through all of them and fix. Probably some modules missing in the system or something mistyped. If you find some mistypes in my tutorial - please send comments to pavel@yepcorp.com - I will fix them ASAP

 

 

Code

 

You are free to do whatever you want with this code. Provided AS-IS.

 

Author

 

Pavel A. Karoukin - program on Perl as hobby and doing PHP work for living. 

 

Comments (0)

You don't have permission to comment on this page.