Docendo discimus

$self->explain

  • Calendar

    May 2017
    M T W T F S S
    « Sep    
    1234567
    891011121314
    15161718192021
    22232425262728
    293031  
  • Archives

  • Recent Posts

  • Bling

Posts Tagged ‘context object’

Catalyst is easy – do the wrong thing

Posted by brunorc on July 13, 2009

Someone landed here while searching for the phrase get catalyst object in model. Before I wrote in one of previous entries:

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

I first searched for the possibilities of doing it. And there are such possibilities, which shouldn’t be surprising, as Catalyst allows one to write all the crap one wants to write – it will just help to write it faster.

So if you really want to use the context object inside your Model (or View), take a look at the ACCEPT_CONTEXT component – and please read the WARNING WARNING WARNING section. In many cases passing some arguments should be sufficient. While being able to do absolutely any crap you can fancy is a nice possibility by itself, it also pays to read the manual of your chainsaw before the first launch. At least you can take advantage of turning the pages with any of your hands.

Having said this, I’d like to point that ACCEPT_CONTEXT even made its way to Catalyst Advent Calendar. It means that sharp tools can be also useful.

P.S. I planned to write about the installation of Catalyst on Windows XP, however it has been postponed (but not forgotten).

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

Catalyst is easy – fourth movement

Posted by brunorc on July 4, 2009

Sooner or later mixing HTML with actual application code will start to look very ugly. While it can be good for some rough drafts, real apps demand something more.

Catalyst treats all views in the same manner, so is really up to you to choose a model. Before using Catalyst I wrote some applications in Mason, so I was really happy being able to use this module as the View layer – also because of its outstanding documentation (and a book). Then I discovered TT (Template Toolkit), which have been used in Catalyst tutorial and I liked it – also because of its outstanding documentation!

I won’t get into details of installation of Mason, TT nor their respective Catalyst::View modules in this post. However, soon I’ll describe the installation procedure on Mac OS X, Ubuntu and (maybe even) Windows. On most systems you can do this using the cpan script. At this moment I assume you are able to install required modules. If not, just glance over this post and get back to it once you know how to install all the necessary stuff (maybe from one of my next posts).

I arbitrary decided to use TT. This is a good choice, not even but especially if you used to work with Mason before. That’s because Template Toolkit is what its name says. Sure, it lacks some capabilities of Mason, but in most cases they are not needed, since Catalyst already provide us with something at least similar. Using TT one is not tempted to stuff the logic into the view layer, acting rather the opposite way – eliminating the code from view, making the latter clean and simple.

But since it have to be easy, I won’t use the bare TT. Instead I will suggest you to use the marvellous TTSite module, which just builds the whole structure you need to have your web application powered by TT templates:

script/helloworld_create.pl view TTEasy TTSite

As you can see, it creates two directories in root, the root directory of documents served by our application. One of them – the src dir – contains all files that should be processed by TT and sent to the user. The other one – lib – is full of the TTSite magic, which moves all the configuration hassle almost out of the way.

DANGER, Will Robinson!

Chances are you won’t limit your Catalyst experience only to my blog. I wholeheartedly advise it to you; however, bear in mind that you will find tons of TT examples, addressing the Catalyst object as c, like [% c.uri_for('blah') %]. Two words of warning then:

  • Catalyst object inside TT is actually the context object, also known as the $c variable (thanks, Diego)
  • the name of this object is actually a config variable, so it can be changed

Having said this, I’m going to use the “c” convention. More on this topic you can read in the Friendly Manual, and remember to see the Synopsis, where examples for both cases are shown.

DANGER AGAIN, Will Robinson!

However, the helper script for View::TTSite assumes we want to use the “Catalyst” object instead of “c”, so it may be a good idea to fix some files already created. To do this, we have to find them first in the root directory (I assume we are in the helloworld directory). We can suppose that word “Catalyst” will be somewhat popular there, so better idea is to search for this word followed by dot; and since we’re going to use grep, we have to escape the dot:

grep -r 'Catalyst\.' root/
root/lib/config/main: IF Catalyst.debug;
root/lib/config/main: MACRO debug(message) CALL Catalyst.log.debug(message);
root/lib/config/url:[% base = Catalyst.req.base;

In the first file we have to change Catalyst to c, but the second one will be more interesting. According to the Friendly Manual, the “c” convention offers us two aliases: base and name. So, in the end, the root/lib/config/url file can be changed from this:

[% base = Catalyst.req.base;
   site.url = {
     base    = base
     home    = "${base}welcome"
     message = "${base}message"
   }
-%]

to this:

[% site.url = {
     base    = base
     home    = "${base}welcome"
     message = "${base}message"
   }
-%]

And now we can restart the server, but we won’t see any difference, because until now we just set the body. And when Catalyst sees that the body is already set, it assumes it shouldn’t be processed by View, thus passing the raw body directly to the client.

So, to enjoy the beautiful TTSIte view we have to try some quick hack – let’s go to the end of the index action of the Root controller and change it a bit:

    #$c->response->body( $template );
    $c->stash->{template} = 'welcome.tt2';

Colours! We have colours! But wait, why it gives us a link to the nonexisting page? Let’s fix it, adding the message action:

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

  $c->stash->{template} = 'message.tt2';
}

Now we can refresh the page and voila! But since there’s a form, why not to fill it and submit?

Ouch. Still no message. But if we open the message.tt2 file we will see that it is passed as a parameter called “message”. So there’s no problem:

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

  $c->stash->{message} = $c->req->params->{message};

  $c->stash->{template} = 'message.tt2';
}

As an exercise for a reader I leave you with changing “Your Name Here” to something more meaningful.

We have just skimmed the topic of View and there’s definitely more to come. But next posts will be probably more focused on the installation of Catalyst and its dependencies.

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

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 »