• 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 and Tatsumaki

Page history last edited by Marco Fontani 13 years, 10 months ago

About Perl

From its site: "Perl is a highly capable, feature-rich programming language with over 20 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 Tatsumaki

Tatsumaki is a port of Tornado for Perl, written by Tatsuhiko Miyagawa. Despite its current alpha status, it's a fully working non-blocking framework. It's based on Plack (a PSGI middleware, inspired by WSGI and Rack) and AnyEvent, a wonderful event loop for Perl.

About ORLite

ORLite is an extremely lightweight SQLite-specific ORM for Perl. It's very useful for simple application prototyping, as in this tutorial.

Goals

At the end of this tutorial, you'll have created a back-end for the SproutCore "tasks" tutorial, using Tatsumaki and ORLite You may even start liking Perl.

Initial setup

Part of this tutorial assumes a Linux OS, but (with some changes) the same can be performed in OSX or Windows. The relevant sqlite3 libraries should also be installed via your favorite package manager.

App::cpanminus will be used to simplify the installation of modules from CPAN. This utility has also been written by Tatsuhiko Miyagawa.

Fetch and install App::cpanminus on ~/perl5, via:

wget http://xrl.us/cpanminus
    chmod +x cpanm

This will fetch the Perl command, and make it executable.

You could also use the cpan client to fetch and install App::cpanminus, or use it throughout to install the other modules.

Installing Tatsumaki is as easy as typing: ./cpanm Tatsumaki.

You'll also need the Plack, JSON, DBD::SQLite and ORLite modules, so the following will work:

./cpanm Plack Tatsumaki JSON DBD::SQLite ORLite

After a bit of crunching in the background, all the various modules should be installed under your ~/perl5 directory.

In order to let Perl look for the modules in ~/perl5, you'll have to add it to the PERL5LIB environment variable, usually done via the following on your shell's .profile, or manually each time:

export PERL5LIB=$HOME/perl5/lib/perl5:$PERL5LIB

In order to test that all's in order, create a new file which eventually will contain the whole "tasks" application, and verify you can reach it via your browser.

Create a file named tasks.tatsumaki.psgi in your favorite folder and editor, and stick in it:

        #!/usr/bin/env perl
        use strict;
        use warnings;

        use Tatsumaki::Error;
        use Tatsumaki::Application;
        use Tatsumaki::Server;

        package Tasks::Controller::List;
        use parent qw/Tatsumaki::Handler/;
        use JSON;

        my $static_data = [
            { guid => '/task/1', isDone => 1,
              description => 'add static data to file' },
            { guid => '/task/2', isDone => 0,
              description => 'test the /tasks list' },
        ];

        sub get
        {
            my $self = shift;
            $self->write( to_json( content => $static_data) );
        }

        package main;
        my $app = Tatsumaki::Application->new([
            '/tasks' => 'Tasks::Controller::List',
        ]);
        return $app;
    

This will create a barebone Tatsumaki application, with one entry point in /tasks, which is supposed to return a list of the tasks. A hashref of static data is initialised, so that you will be able to test it with Plackup straightaway:

plackup -a tasks.tatsumaki.psgi

You'll then be able to visit http://127.0.0.1:5000/tasks, and you should receive the list of the static tasks, in JSON notation.

SproutCore proxy modifications

You'll need to tell sc-server that from now onwards the path /tasks will need to be redirected to 127.0.0.1:5000. You can do so by adding the following lines to your already-existing Buildfile:

    proxy '/tasks', :to => '127.0.0.1:5000'
    proxy '/task',  :to => '127.0.0.1:5000'

This will ensure that both /tasks and /task will be redirected to your Plackup server.

Remove your tmp/ folder and re-launch sc-server. You should be able to see the list of tasks defined in the above .psgi file, although you won't be able to interact with them yet.

Database

In order to make this as simple as possible, you'll just create a new SQLite database on /tmp, create a Task table on it and add some data to it:

    $ sqlite3 /tmp/tasks.sqlite
    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,'Insert stuff in table');
    sqlite> INSERT INTO Task (isdone,description) VALUES
                 (0,'Get a coffee');
    sqlite> INSERT INTO Task (isdone,description) VALUES
                 (0,'Finish documentation');

Press CTRL+D to exit the SQLite prompt. That's all.

Hook ORLite

Back to our tasks.tatsumaki.psgi file, insert the following before the package Tasks::Controller::List; line:

        package Tasks::Model;
        use ORLite {
          'package' => 'Tasks::Model',
          'file'    => '/tmp/tasks.sqlite',
        };
    

This will allow us to use the Tasks::Model::Task class to interact with the table, and it will use the database file we just created.

List the tasks

Back to our tasks.tatsumaki.psgi file, remove the $static_data definition and the body of sub get, effectively replacing the package Tasks::Controller::List contents with:

        package Tasks::Controller::List;
        use parent qw/Tatsumaki::Handler/;
        use JSON;
        # the controller used to GET a list of tasks, or POST a new task.

        # GET a list of all tasks
        sub get {
            my ( $self, $query ) = @_;
            my @tasks;
            Tasks::Model::Task->iterate(
                sub {
                    push @tasks, {
                        guid        => '/task/' . $_->id,
                        description => $_->description,
                        isDone      => $_->isdone ? 1 : 0,
                    };
                }
            );
            $self->write( to_json( { content => \@tasks } ) );
        }
    

If you restart plackup via plackup -a tasks.tatsumaki.psgi and restart the SproutCore sc-server, you should be able to see the list of tasks pulled from the database.

GETting one task's data

This action is performed via a GET action to /task/NNN, where NNN is the guid given by the list. At the bottom of the tasks.tatsumaki.psgi file, under package main;, add a handler for /task/NNN. The package main; should look like this:

        package main;
        my $app = Tatsumaki::Application->new(
            [
                '/tasks'      => 'Tasks::Controller::List',
                '/task/(\d+)' => 'Tasks::Controller::Content',
            ]
        );
        return $app;
    

Above in the file, after the sub get we modified in the previous step, add a new package to handle the /task/NNN request:

        package Tasks::Controller::Content;
        use parent qw/Tatsumaki::Handler/;
        use JSON;
        # GET a task's data
        sub get {
            my ( $self, $id ) = @_;
            my $task;
            eval { $task = Tasks::Model::Task->load($id); };
            Tatsumaki::Error::HTTP->throw(404) if ($@);    # not found in db
            $self->write(
                to_json(
                    {
                        content => {
                            guid        => '/task/' . $task->id,
                            description => $task->description,
                            order       => $task->id,
                            isDone      => $task->isdone,
                        }
                    }
                )
            );
        }
    

You can now re-launch plackup as before, then open http://127.0.0.1:5000/task/1 in your browser. You should see the JSON representation of the first task that was added in the SQLite database.

Adding a new task

This action is performed via a JSON POST request to /tasks, which should contain an hashref containing the isDone and description values to be added to the database. On success, the method should return a HTTP 201 status, and a 'Location' header containing the relative URL of the resource that has just been created. Inside the Tasks::Controller::List package, below the sub get, insert the following:

        # POST a new task
        sub post {
            my ($self) = @_;
            my $data;
            my $json = JSON->new;
            $json->allow_barekey(1);
            $json->allow_singlequote(1);
            eval {
                $data = $json->decode( $self->request->content );
            };
            # not a JSON, or can't parse:
            Tatsumaki::Error::HTTP->throw(400) if ($@);
            Tatsumaki::Error::HTTP->throw(400)
                unless (
                    defined $data->{description}
                    and defined $data->{isDone}
                );
            my $task = Tasks::Model::Task->create(
                isdone => $data->{isDone} ? 1 : 0,
                description => $data->{description},
            );
            $self->response->status(201);
            $self->response->headers([
                 'Location' => '/task/' . $task->id,
            ]);
        }
    

If you restart plackup and sc-server, you should be able to press the Add Task button in the interface and see the New task popping up. You won't be able to edit it yet, though.

Editing a task

This action is performed via a JSON PUT request to /task/NNN, which should contain an hashref of either isDone, description, or both. The task identified by the number NNN should be updated with the new information given via the request. We can return the same Location header and HTTP status as the POST we just saw. Inside the Tasks::Controller::Content package, below the sub get, insert the following:

        # PUT new details for a task (isDone or description)
        sub put {
            my ( $self, $id ) = @_;
            my $task;
            eval { $task = Tasks::Model::Task->load($id); };
            Tatsumaki::Error::HTTP->throw(404) if ($@);    # not found in db
            my $json = JSON->new;
            $json->allow_barekey(1);
            $json->allow_singlequote(1);
            my $data;
            eval {
                $data = $json->decode( $self->request->content );
            };
            # not a JSON, or can't parse
            Tatsumaki::Error::HTTP->throw(400) if ($@);
            Tatsumaki::Error::HTTP->throw(400)
                unless (
                    defined $data->{description}
                    or defined $data->{isDone}
                );
            my $new_isdone =
                defined $data->{isDone}
                ? int( $data->{isDone} )
                : $task->isdone;
            my $new_desc   =
                defined $data->{description}
                ? $data->{description}
                : $task->description;
            Tasks::Model->do(
                'UPDATE Task SET isDone=?, description=? WHERE id=?',
                {}, $new_isdone, $new_desc, $task->id,
            );
            $self->response->status(201);
            $self->response->headers([
                'Location' => '/task/' . $task->id,
            ]);
        }
    

This time you'll be able to restart plackup, remove the SproutCore's tmp/ directory, restart sc-server and finally be able to edit a task, and mark it as done. You won't be able to delete it yet.

Deleting a task

This action is performed via a DELETE request to /task/NNN. As long as the task exists, we'll return a HTTP 200 OK status and purge the task from the database. An HTTP 404 Not Found error will be returned if the task cannot be found on the database, etc. Still inside the Tasks::Controller::Content package, below the just-inserted sub put, insert the following:

        # DELETE a task
        sub delete {
            my ( $self, $id ) = @_;
            my $task;
            eval { $task = Tasks::Model::Task->load($id); };
            Tatsumaki::Error::HTTP->throw(404) if ($@);    # not found in db
            $task->delete;
            $self->response->status(200);
            $self->write('');
        }
    

That should be all: you'll now be able to list, add, edit, and delete tasks.

Code

A full BSD-licensed copy of the full code for this example can be found at http://darkpan.com/files/tasks.tatsumaki.psgi.txt.

You're Done!

You can now continue to the next step:Step 7: Hooking Up to the Backend.

Comments (0)

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