Docendo discimus

$self->explain

  • Calendar

    July 2017
    M T W T F S S
    « Sep    
     12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31  
  • Archives

  • Recent Posts

  • Bling

Posts Tagged ‘stash’

Top Searches (July)

Posted by brunorc on July 27, 2009

Update: Diego pointed out something very important, so this post had to be significantly updated.

Someone came here searching for catalyst mason variables stash. It brought me to the question: how do I access stash from the Mason view? The answer is: the same way as in TT(Site) view. Mason page is just passed the stash content, so it’s enough to declare variables we want to use in our view.

A small example: let’s put something like this in our controller:

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

    $c->stash->{message} = 'We will feast on manflesh!';

    $c->stash->{template} = 'uruk_hai.html';
}

Now if we want to use TT(Site), our template will look like this:

<h2>[% message %]</h2>

In Mason it looks like this:

<%args>
# don't die if there's no message
$message => undef
</%args>
<h2><% $message %></h2>

Moreover, the Catalyst::View::Mason module declares some useful globals, like $c, $base and $name, so we can choose between Mason and TT without sacrificing anything.

Posted in Catalyst for intimidated | Tagged: , , , , , , , , | 2 Comments »

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 »