Docendo discimus

$self->explain

  • Calendar

    June 2009
    M T W T F S S
    1234567
    891011121314
    15161718192021
    22232425262728
    2930  
  • Archives

  • Recent Posts

  • Bling

Archive for June, 2009

Catalyst is easy – third movement

Posted by brunorc on June 27, 2009

Wherever you go to read about Catalyst you will find it is an MVC framework. But we are still digging in the same file, aren’t we? Everything the application does is stuffed into one controller file. Now we are going to change it and isolate the model.

Model is responsible for providing the data. Very often it will be some database wrapping module, but data can be fetched from everywhere: filesystem, mail queue, IMAP, LDAP, Web Service, image generator/transformator, version control system, RSS feed… However, model shouldn’t be identified with the data itself. All the facilities – like formatting or consistency checks – should be done inside the model, not in the application. Then the model can be reused while writing e.g. maintenance scripts. Actually, Catalyst model classes should only act as a glue code between the application and the real model, which shouldn’t be related with Catalyst.

In our case the data consists of one list, but recently we isolated some code used for adding new items. We can put this code in the separate module and use all model-related facilities of Catalyst. Of course, we need a model for this:

script/helloworld_create.pl model Simple

and the stub of the model will be created in the file lib/helloworld/Model/Simple.pm. The file itself is not very interesting, since all the necessary code is inherited from Catalyst::Model class.

We would like our model to access the “raw data” (in this case – the list), to return all items, to add a new one and maybe to do the search. Let’s start with =cutting the documentation under the description and putting some stubs:

package helloworld::Model::Simple;

use strict;
use warnings;
use parent 'Catalyst::Model';

=head1 NAME

helloworld::Model::Simple - Catalyst Model

=head1 DESCRIPTION

Catalyst Model.

=cut

my @raw_data = qw/beer bacon pierogi/;

sub get_all {
  my ( $self ) = @_;

  return @raw_data;
}

sub add_new {
  my ( $self, @newitems ) = @_;

  push @raw_data, @newitems;
}

Notice that methods of the model don’t have access to the Catalyst context object, because, in fact, they shouldn’t have it.

Now we can check how it works. Until now, the code in the index action looked like this:

sub index :Path :Args(0) {
    my ( $self, $c ) = @_;

    my $list = '';

    foreach my $item ( @list ) {
        $list .= "<li>$item</li>\n";
    }
# (...)

Let’s change line 6 of this example:

    foreach my $item ( $c->model('Simple')->get_all ) {

Similarly, we can change this line of the _add action:

    push @items, @new_items;

to this:

    $c->model('Simple')->add_new(@new_items);

Why bother? Let’s assume that there’s a need to have a unique list of items. It can be achieved easily, converting the array into the hash, without changing a single line in the controller. This is the code of the new version of the model:

my %raw_data = map { $_ => 1 } qw/beer bacon pierogi/;

sub get_all {
  my ( $self ) = @_;

  return keys %raw_data;
}

sub add_new {
  my ( $self, @newitems ) = @_;

  $raw_data{$_} = 1 foreach @newitems;
}

Now it is possible to move the code from the private action to the model. However, there’s no $c variable received by model methods. And since there’s no context, there will be no possibility to populate the stash directly. No problem at all:

$c->stash->{add_result} = $c->model('Simple')->add_new($whatever);

But how to stuff the message and count in one variable? Oh, and probably we would like to notify user – just in case – that he tried to add the value already present in the list. Returning the hash reference allows one to pass anything necessary. This may be not the best option, but at least it gives some reason to mention complex data structures.

After this, the controller will look like this:

sub api_add :Local :Args(1) {
    my ( $self, $c, $newitem ) = @_;

    if ( $c->req->method eq 'POST' ) {
        $c->stash->{add_result} = $c->model('Simple')->add_new($newitem);

        $c->res->body( $c->stash->{add_result}->{count} ? "OK" : "ER" );
    }
    else {
        $c->res->status(405);
        $c->res->body('ER');
    }
}

sub add :Local :Args(0) {
    my ( $self, $c ) = @_;

    my $optional;

    if ( $c->req->params->{newitem} ) {
        $c->stash->{add_result} = $c->model('Simple')->add_new( $c->req->params->{newitem} );

        if ( $c->req->params->{addit} eq 'Add it!' and $c->stash->{add_result}->{count} ) {
            my $count = $c->stash->{add_result}->{count};
            $optional = "<h4>Thank you for adding $count items!</h4>";
        }

        if ( $c->stash->{add_result}->{message} ) {
            $optional .= '<h4>' . $c->stash->{add_result}->{message} . '</h4>';
        }
    }
    # template part skipped
}

All the logic is now embedded into the model class:

sub add_new {
  my ( $self, $newitem ) = @_;

  my $result = {};

  if ( $newitem =~ s/_//g ) {
      $result->{message} = 'Removed illegal underscore(s). ';
  }

  my @newitems = split / /, $newitem;

  my @okitems = grep { not exists $raw_data{$_} } @newitems;
  
  $raw_data{$_} = 1 foreach @okitems;

  $result->{count} = @okitems;

  $result->{message} .= 'Skipped ' . ( @newitems - @okitems ) . ' doubles'
    if scalar @newitems != scalar @okitems;

  return $result;
}

but why to put the result in the stash? At the moment it is used only internally, but in fact, stash is the way to propagate the information from Model into View. The application has no view yet, but soon it will change. Generating HTML inside the code is clumsy and once we get tired of it, Catalyst is eager to relieve us of this ugliness.

Posted in Catalyst for intimidated | Tagged: , , , , , , | 1 Comment »

Catalyst is easy – rondo alla API

Posted by brunorc on June 21, 2009

I’ve already shown that Catalyst is able to process some user input. Really! And not only process, it can even discover if the form was really filled. And since you’re going to write web applications, you probably know that there is no spoon, no browser and no user on the other side of the request. Or, at least, there may be no user.

And if there is no user, what’s the sense of showing the form? There may be another action, designed especially for non-human users. Or, to avoid being accused of racism – some formless users.

This action needs to make use of another attribute – Args – because it needs to receive one argument, which would be the string that is expected to be added to the list. The minimal code for this action could look like this:

sub add :Local :Args(1) {
    my ( $self, $c, $newitem ) = @_;

    push @items, $newitem;
}

except it wouldn’t work, because every action has to have distinct name; api_add looks like a nice name, so after renaming the action we can try.

We can try to see the error message.

OK, that was intentional. But if we are here, why not to spend a while looking at it and try to understand it? First there’s an error message, that informs us that Catalyst could not find a view to forward to. But the application has no view at the moment, so it is not strange Catalyst couldn’t have found it. Anyway, in case it could help us resolving the error, Catalyst shows us four important objects it keeps in its context: Request and Response (which were mentioned in the previous part) along with Stash and Config. While Config sounds rather obvious – it apparently holds the configuration data for the application – Stash requires some explanation: it is just a big bag we can use for the data used while creating the Response.

This problem can be solved if there’s some output, passed to the body:

sub api_add :Local :Args(1) {
    my ( $self, $c, $newitem ) = @_;

    push @items, $newitem;

    $c->res->body("OK");
}

Now it works, but the code pushing the new item into list is doubled. What if there is a new requirement which says “no underscore in item names, and treat spaces as separators”? This code should be isolated, and both actions should only act as wrappers. As always, Catalyst makes it easy:

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

    if ( $c->stash->{newitem} =~ s/_//g ) {
        $c->stash->{message} = 'Removed illegal underscore(s). ';
    }

    my @new_items = split / /, $c->stash->{newitem};

    push @items, @new_items;

    $c->stash->{count} = @new_items;
}

There is a new action attribute: Private. It means that this action cannot be called directly by using URL, it is reachable only from other actions. And since those actions call the _add action, they have to pass some parameters and – last but not least – they would probably like to know what happened. Hence the use of stash; action gets its parameter newitem from the stash, and then populates the stash with an optional message and the information about the count of added items.

sub api_add :Local :Args(1) {
    my ( $self, $c, $newitem ) = @_;

    $c->stash->{newitem} = $newitem;

    $c->forward('_add');

    $c->res->body( $c->stash->{count} ? "OK" : "ER" );
}

Now the api_add action doesn’t do much: it just packs the $newitem into stash and then creates some response, based on the count of added objects. It also uses the forward method to call the private action. The same will happen to the add action:

sub add :Local :Args(0) {
    my ( $self, $c ) = @_;

    my $optional;

    if ( $c->req->params->{newitem} ) {
        $c->stash->{newitem} = $c->req->params->{newitem};

        $c->forward('_add');

        if ( $c->req->params->{addit} eq 'Add it!' and $c->stash->{count} ) {
            my $count = $c->stash->{count};
            $optional = "<h4>Thank you for adding $count items!</h4>";
        }

        if ( $c->stash->{message} ) {
            $optional .= '<h4>' . $c->stash->{message} . '</h4>';
        }
    }

    my $template = qq[
<html>
    <head>
        <title>Add new item</title>
    </head>
    <body>
        <div align="center">
            <h3>Add something!</h3>
            <form method="get">
                <input type="text" size="24" id="newitem" name="newitem">
                <input type="submit" id="addit" name="addit" value="Add it!">
            </form>
            $optional
        </div>
    </body>
</html>
];

    $c->response->body( $template );
}

Although the forward method passes the @_ list as well as the whole $c object, it is a good idea to extract the newitem and store it into stash, instead of doing it inside the _add action. Again, this is the separation of concerns – wrapper unwraps (isn’t it logical?) the parameters, while the actual action just acts on well defined content of the stash.

Coda

The whole thing about Args attribute and the API stuff were quite nice, but pretty wrong. Now the application has an action that changes its state (in other words – the action has some side effects), and this action is accessible using the GET request. What if someone bookmarks this URL and then use it repeatedly? It can of course be solved by not allowing to insert the same item twice (how?), but that way we handle the effect, not the cause. That said, the action can be improved easily:

sub api_add :Local :Args(1) {
    my ( $self, $c, $newitem ) = @_;

    if ( $c->req->method eq 'POST' ) {
        $c->stash->{newitem} = $newitem;

        $c->forward('_add');

        $c->res->body( $c->stash->{count} ? "OK" : "ER" );
    }
    else {
        $c->res->status(405);
        $c->res->body('ER');
    }
}

Status code 405 means “Method not allowed”, so it is perfect for this situation. To check the if the code works as expected we need something more than a browser. There is a command line utility called lwp-request, which comes with Perl LWP module and even has some documentation, so I’ll just use it:

$ lwp-request -se http://localhost:3000/api_add/beer          
405 Method Not Allowed
Connection: close
Date: Sun, 21 Jun 2009 17:06:41 GMT
Content-Length: 2
Content-Type: text/html; charset=utf-8
Client-Date: Sun, 21 Jun 2009 17:06:41 GMT
Client-Peer: 127.0.0.1:3000
Client-Response-Num: 1
Status: 405
X-Catalyst: 5.80005

ER

Fails as expected! Now the proper incantation (when lwp-request asks for entering content, just press Ctrl D):

$ lwp-request -se -m POST http://localhost:3000/api_add/beer
Please enter content (application/x-www-form-urlencoded) to be POSTed:
200 OK
Connection: close
Date: Sun, 21 Jun 2009 17:06:53 GMT
Content-Length: 2
Content-Type: text/html; charset=utf-8
Client-Date: Sun, 21 Jun 2009 17:06:53 GMT
Client-Peer: 127.0.0.1:3000
Client-Response-Num: 1
Status: 200
X-Catalyst: 5.80005

OK

This is rather coarse way of testing the application. But Catalyst also provides one with sophisticated testing tools, however it is a different topic.

Posted in Catalyst for intimidated | Tagged: , , , , , , , , , | Leave a Comment »

Catalyst is easy, second movement

Posted by brunorc on June 21, 2009

After some digressions I get back to the main course – the Catalyst itself. I promised to show something more dynamic, so here we go.

It is obvious that at some point application needs to get some input from the user. It is usually done by showing the form to the user, and then using the data user puts into the form. Showing the form should be rather easy – let’s start from copying “header” and “footer” of the index action, renaming the action to add:

sub add :Path :Args(0) {
    my ( $self, $c ) = @_;

    $c->response->body( $template );
}

Of course the Catalyst server was ran with the -r flag, this code will die immediately, since the variable $template was not defined. This can be easily solved by putting some HTML code in the mentioned variable:

sub add :Path :Args(0) {
    my ( $self, $c ) = @_;

    my $template = qq[
<html>
    <head>
        <title>Add new item</title>
    </head>
    <body>
        <div align="center">
            <h3>Add something!</h3>
            <form method="get">
                <input type="text" size="24" id="newitem" name="newitem">
                <input type="submit" id="addit" name="addit" value="Add it!">
            </form>
        </div>
    </body>
</html>
];

    $c->response->body( $template );
}

Now we are ready to visit the URL http://localhost:3000/add… huh?

Yeah, page not found.

Let us take a look at the output of our server:

[info] *** Request 4 (0.028/s) [3055] [Sun Jun 21 14:41:35 2009] ***
[debug] "GET" request for "add" from "127.0.0.1"
[debug] Arguments are "add"
[info] Request took 0.004999s (200.040/s)
.------------------------------------------------------------+-----------.
| Action                                                     | Time      |
+------------------------------------------------------------+-----------+
| /default                                                   | 0.000192s |
| /end                                                       | 0.000275s |
'------------------------------------------------------------+-----------'

Looks like Catalyst server launched the default action. When it gets hit with the request, server tries to dispatch the URL into action, and if everything fails, the default action of the Root controller is used. It is done by checking actions’ attributes, like Path or Args. Both index and add have the empty Path attribute, and that’s why the add was not launched.

Now there are two basic options:

The attribute Local will make the action (yes, you guessed it!) local to the current controller, thus making the action reachable. If we make the action local, it will be bound to the containing controller. If the action dump of the controller Core is local, after renaming the controller to Kernel the URL /core/dump will become unreachable, and the action will be available under /kernel/dump. After making the add action local, the output of starting Catalyst server will look like this:

[debug] Loaded Private actions:
.----------------------+--------------------------------------+--------------.
| Private              | Class                                | Method       |
+----------------------+--------------------------------------+--------------+
| /add                 | helloworld::Controller::Root         | add          |
| /default             | helloworld::Controller::Root         | default      |
| /end                 | helloworld::Controller::Root         | end          |
| /index               | helloworld::Controller::Root         | index        |
'----------------------+--------------------------------------+--------------'

[debug] Loaded Path actions:
.-------------------------------------+--------------------------------------.
| Path                                | Private                              |
+-------------------------------------+--------------------------------------+
| /                                   | /default                             |
| /                                   | /index                               |
| /add                                | /add                                 |
'-------------------------------------+--------------------------------------'

The attribute Path has to contain (yes, you guessed it again!) the path under which the action will be available. Now nobody can stop us from making the add action reachable under some really fancy URL:

sub add :Path('/now/you/can/add') :Args(0) {

and then enjoying the following output:

[debug] Loaded Private actions:
.----------------------+--------------------------------------+--------------.
| Private              | Class                                | Method       |
+----------------------+--------------------------------------+--------------+
| /add                 | helloworld::Controller::Root         | add          |
| /default             | helloworld::Controller::Root         | default      |
| /end                 | helloworld::Controller::Root         | end          |
| /index               | helloworld::Controller::Root         | index        |
'----------------------+--------------------------------------+--------------'

[debug] Loaded Path actions:
.-------------------------------------+--------------------------------------.
| Path                                | Private                              |
+-------------------------------------+--------------------------------------+
| /                                   | /default                             |
| /                                   | /index                               |
| /now/you/can/add                    | /add                                 |
'-------------------------------------+--------------------------------------'

There is really no distinction in the Private actions section, but when it comes to Path actions, the difference is quite easy to spot. In this example I decided to use the Local attribute.

Now the action should at last work. After filling the textbox we can hit the button, seeing the server coping with the data we provided:

[info] *** Request 2 (0.667/s) [3107] [Sun Jun 21 15:16:38 2009] ***
[debug] Query Parameters are:
.-------------------------------------+--------------------------------------.
| Parameter                           | Value                                |
+-------------------------------------+--------------------------------------+
| addit                               | Add it!                              |
| newitem                             | magic                                |
'-------------------------------------+--------------------------------------'
[debug] "GET" request for "add" from "127.0.0.1"
[debug] Path is "add"
[info] Request took 0.004661s (214.546/s)
.------------------------------------------------------------+-----------.
| Action                                                     | Time      |
+------------------------------------------------------------+-----------+
| /add                                                       | 0.000187s |
| /end                                                       | 0.000324s |
'------------------------------------------------------------+-----------'

which is, of course, impressive. But it doesn’t add anything at all! If we go to the “main page”, we will see the same list. That should be changed, so the add action can modify the list of items. To achieve this, first we have to make the list available for the action, making it global:

my @items = ( 'beer', 'bacon', 'pierogi' );

sub index :Path :Args(0) {
    my ( $self, $c ) = @_;

    my $list = '';

    foreach my $item ( @items ) {
        $list .= "<li>$item</li>\n";
    }

    my $template = qq[
<html>
    <head>
        <title>My items</title>
    </head>
    <body>
        <ul>
        $list
        </ul>
        <p>Only \$5!</p>
    </body>
</html>
];

    $c->response->body( $template );
}

Now the second action can modify the list:

sub add :Local :Args(0) {
    my ( $self, $c ) = @_;

    if ( $c->req->params->{newitem} ) {
        push @items, $c->req->params->{newitem};
    }

    my $template = qq[
<html>
    <head>
        <title>Add new item</title>
    </head>
    <body>
        <div align="center">
            <h3>Add something!</h3>
            <form method="get">
                <input type="text" size="24" id="newitem" name="newitem">
                <input type="submit" id="addit" name="addit" value="Add it!">
            </form>
        </div>
    </body>
</html>
];

    $c->response->body( $template );
}

The $c variable is called “context”. Usually if you want to get something from Catalyst, first try to ask the context object if it has seen it. Inside of the context one can find the request and response object (with their req and res aliases for those of us who lost the war with laziness). Request may contain some parameters, called params; and those parameters are accessed by their respective names. This may be used to write the code like this:

sub add :Local :Args(0) {
    my ( $self, $c ) = @_;

    my $optional;

    if ( $c->req->params->{newitem} ) {
        push @items, $c->req->params->{newitem};

        if ( $c->req->params->{addit} eq 'Add it!' ) {
            $optional = '<h4>Thank you for filling the form!</h4>';
        }
    }

    my $template = qq[
<html>
    <head>
        <title>Add new item</title>
    </head>
    <body>
        <div align="center">
            <h3>Add something!</h3>
            <form method="get">
                <input type="text" size="24" id="newitem" name="newitem">
                <input type="submit" id="addit" name="addit" value="Add it!">
            </form>
            $optional
        </div>
    </body>
</html>
];

    $c->response->body( $template );
}

Next time I will dwell a little bit on the second attribute – Args; that in turn will be useful for creating the API, so you would be able to build Facebook, in case it wasn’t built already.

Posted in Catalyst for intimidated | Tagged: , , , , , , , | 1 Comment »