Writing a Dancer Plugin

Posted in: Technical Track

Last week I was looking at what Dancer had to offer in term of cache plugins. I found Dancer::Plugin::Memcached which had a pretty nice interface, but was unfortunately using a backend that isn’t available on my server. So I thought: “how hard can it be to write a similar plugin that interfaces with CHI?”.

Not very hard, it turns out, and a few hours later Dancer::Plugin::Cache was born.

Writing a Plugin

All the tools you need to write a Dancer plugin are contained in he helper module Dancer::Plugin. To invoke them, you just need to ‘use’ Dancer::Plugin within your module — all the inheritance stuff is taken care of behind the curtain:

package Dancer::Plugin::Cache;
use strict;
use warnings;
use Dancer ':syntax';
use Dancer::Plugin;

With this simple invocation, we have now at our disposal the three functions that module provides: register, plugin_setting and register_plugin.

register()

register() is the most importance of the bunch, and defines keywords that are imported in the application’s namespace when the plugin is used. For example, for providing a ‘cache‘ keyword returning the CHI instance of the application, what we need to do is:

register cache => sub {
    state $cache = CHI->new(%{ plugin_setting() });
    return $cache;
};

Which is not high magic, mind you; we’re only providing a wrapper around the underlaying cache object.

The fun, however, doesn’t have to end there. Such keywords can be used to inject some new route behaviors to the application as well:

register check_page_cache => sub {
    before sub {
        halt cache()->get(request->{path_info});
    };
};

In this case, calling ‘check_page_cache’ in the application will enable a check that, if available, will return the cached output of a route instead of executing it.

Also, since the calls to register are done at runtime, we can also indulge in the kind of fun that Moose allows and programmatically create routes:

# create a bunch of helper functions
for my $method ( qw/ set get clear compute / ) {
    register 'cache_'.$method => sub {
        return cache()->$method( @_ );
    }
}

plugin_setting()

If you noticed, I already used plugin_setting() in the definition of the ‘cache‘ keyword. It has a very simple function: it returns the configuration hash related to the current plugin. For Dancer::Plugin::Cache, it’ll return whatever is contained within config->{plugins}{Cache}, which in the YAML configuration file will be, e.g.:

plugins:
    Cache:
        driver: Memory
        global: 1

register_plugin()

This last function is the final amen of the plugin that tells Dancer to go forth and register it with the application. There are no knobs or settings that we have to be aware of. It just needs to be there at the end of the plugin module, and everything will be peachy.

register_plugin;
1;
__END__

Ready to Rock

And that’s it, we are ready to use our plugin (see the source of Dancer::Plugin::Cache for the full working example):

package MyApp;
use Dancer ':syntax';
use Dancer::Plugin::Cache;
# caching pages' response
check_page_cache;
# this page will be automatically cached
get '/cache_me' => sub {
    cache_page template 'foo';
};
# but not this one
get '/uncached' => sub {
    template 'bar';
};
# using our helper functions
get '/clear' => sub {
    cache_clear;
};
put '/stash' => sub {
    cache_set secret_stash => request->body;
};
get '/stash' => sub {
    return cache_get 'secret_stash';
};
# using the cache directly
get '/something' => sub {
    my $thingy = cache->compute( 'thingy', sub { compute_thingy() } );
    return template 'foo' => { thingy => $thingy };
};

A last, small detail that is good to know: the plugin must be ‘use’d in every module of the app where you want to access its keywords.

email
Want to talk with an expert? Schedule a call with our team to get the conversation started.

5 Comments. Leave new

Damien Krotkine
March 30, 2011 10:37 am

Nice demonstration on how easy it is to create a Dancer plugin. However, I think it’d be better to call it Dancer::Plugin::Cache::CHI or Dancer::Plugin::CHI.

It makes sense to have CHI in the name, and to not “squat” the Dancer::Plugin::Cache name, which is very generic. What do you think ?

Reply
Yanick Champoux
March 30, 2011 11:02 am

In this case, I think the generic naming makes sense, as CHI is aimed at being the generic way of interfacing with any cache system, the same way DBI is for databases. Buuut I’m admittedly biased and tainted by my secret agenda for world domination. ;-)

Seriously, though, I could easily be coaxed into migrating the module to Dancer::Plugin::Cache::CHI (which I would prefer over P::C::CHI, as the latter don’t carry the information that it’s a caching module for anyone not already acquainted with CHI). Do you know how the rest of the Dancer core team feels about it?

Reply
Yanick Champoux
March 30, 2011 11:32 am

follow-up: Sukria also thinks D:P:C is too generic. I bow down to the wisdom of the Cha-cha Cabal (or the Mambo Meisters? Lambada Leaders?), and shall migrate the plugin.

Btw, mucho thanks for the feedback!

Reply
Damien Krotkine
March 31, 2011 3:47 am

Thank *you* for understanding the namespace potential issues, and agreeing to change the name :) We try to communicate with plugin authors to leave freedom of inovation but keep homogeneous namespaces and equality.

Dancer::Plugin::Cache::CHI is a perfect name !

And there is no Cabal. (I think).

Reply
Yanick Champoux
March 31, 2011 9:42 am

> Thank *you* for understanding the namespace potential issues, and agreeing to change the name :)

You’re very welcome. The request was sensible, and the tone as civil as can be. I would have been a certified lout to off-handily dismiss it. :-)

> And there is no Cabal. (I think).

Oh, I didn’t mean anything negative by that, but merely meant the Dancer devs core. Y’know, the guys at the head of Dancer’s beautiful conga line. :-)

Reply

Leave a Reply

Your email address will not be published. Required fields are marked *