Stuffing your stash with closures
The stash
Every request in Catalyst has a stash, which is scoped to that request.
It's accessed with the $c->stash
accessor inside Catalyst, and made
available within whatever View you are using in your application.
For the purposes of this article, I'll concentrate on Template, i.e. TT, but this technique is widely applicable.
The usual pattern is to stash various data (simple scalars, objects, arrays, etc) during the course of your request, and you can then retrieve them (and for more complex data, like objects, call methods on them), once inside your template.
Example of trivial usage
sub foo : Local { my ($self, $c) = @_; $c->stash( things => $c->model('DB::Things')->search({ bar => $c->request->param('bar') }) ); } .... <ul> [% FOREACH thing IN things.all %] <li>[% thing.name %]</li> [% END %] </ul>
Stashing closures
So, you can stash simple scalars, arrays and hashes (and then iterate through them later), objects (which you can then call methods on later), but less obviously, you can stash code references, and then call that code later..
Trivial example
sub foo : Local { my ($self, $c) = @_; $c->stash( thing => sub { return 'quux' } ); } .... [% thing() %] prints: quux
So what's this useful for?
Well, less trivial cases, of course!!
$c->stash( uri_for_secure => sub { my $uri = $c->uri_for(@_); $uri->scheme('https'); return $uri } ); [% c.uri_for_secure(...your normal params here...) %]
Hey, that's useful, eh?
Danger Will Robinson!!!
THE EXAMPLE ABOVE IS UNSAFE. It causes circular references!
Closing over $c
(i.e. making a sub reference that includes $c
inside it),
and then putting it into a data structure $c->stash
which is directly
connected to $c
, causes a loop, meaning that perl won't ever de-allocate
the memory.
If you do this once per request, then your application will quickly run out of memory!!
ContextClosure
with 'Catalyst::Component::ContextClosure'; sub foo : Local { my ($self, $c) = @_; $c->stash( uri_with_secure => $self->make_context_closure(sub { my ($c) = @_; my $uri = $c->uri_for(@_); $uri->scheme('https'); return $uri; }, $c) ); }
Whilst this looks somewhat more baroqe than the previous version, it won't leak any memory, and is tremendously useful.
Sugar
The latest (0.36) version of Catalyst::View::TT has some syntax sugar to make adding closures much easier. You don't get the flexibility of being able to change them during the request cycle, but for most cases that isn't needed.
In your view configuration, you declare which methods on your view class will be exposed:
expose_methods => [qw/uri_for_css/],
and you then implement the method (in your View class), without having to worry about circular references through the context object, E.g.
sub uri_for_css { my ($self, $c, $filename) = @_; # Additional complexity here - this is a trivial example! return $c->uri_for('/static/css/' . $filename); }
Further thoughts and ideas.
Other great things about a stashed closures is that you can have entirely different implementations of them for different parts of your site (without needing to add a method on your MyApp object with horrible conditional logic in it).
If you're using Chained dispatch, then you can fill in a default closure at the base of your chain (for generic requests), and replace it with more specific versions later in the dispatch cycle for more specific / customised parts of your application.
As the closures are code refs, it is in fact entirely possible to further customise them by wrapping them inside another code ref which does some further work.
For example (building in the ContextClosure
example):
my $old = $c->stash->{uri_with_secure}; $c->stash( uri_with_secure => sub { my $uri = $old->(@_); $uri->path('/sekrit' . $uri->path); return $uri }); .... [% uri_with_secure('...') %] => forces https, adds /sekrit to the front of any paths generated by uri_for
Conclusion
I hope this short article has opened your eyes to the potential useages for closures to clean up repeated template logic fragments within your application, and given you some ideas for alternatative ways to abstract specific fragments of display logic which don't really belong in your application templates.
Author
Tomas (t0m) Doran <bobtfish@bobtfish.net>
.