For [$work](http://linkfluence.net/), I've been working on a job queue system, using Moose, Catalyst (for a REST API) and DBIx::Class to store the jobs and some meta (yeah I know, there is not enough job queue system already, the world really needs a new one ...).
Basicaly, I've got a XXX::Worker class that all the workers extends. This class provide methods for fetching job, add a new job, mark a job as fail, retry, ...
The main loop in the XXX::Worker class look like this:
```perl
# $context is a hashref with some info the job or method may need
while (1) {
my @jobs = $self->fetch_jobs();
foreach my $job (@jobs) {
my $method = $job->{funcname};
$self->$method($context, $job);
}
$self->wait;
}
```
and the worker look like this
```perl
package MyWorker;
use Moose;
extends 'XXX::Worker';
sub foo {
my ($self, $context, $job) = @_;
# do something
$self->job_success();
}
```
But as I'm using Moose, I want to add more sugar to the syntax, so writing a new worker would be really more easy.
Here comes [Devel::Declare](http://search.cpan.org/perldoc?Devel::Declare).
The syntax I want for my worker is this one:
```perl
work foo {
$self->logger->info("start to work on job");
# do something with $job
};
work bar {
# do something with $job
};
success foo {
$self->logger->info("woot job success");
};
fail bar {
$self->logger->info("ho noez this one failed");
};
```
Where with `work` I write the code the writer will execute on a task, `success`, a specific code that will be executed after a job is marked as successfull, and `fail` for when the job fail.
I will show how to add the `work` keyword. I start by writing a new package:
```perl
package XXX::Meta;
use Moose;
use Moose::Exporter;
use Moose::Util::MetaRole;
use Devel::Declare;
use XXX::Meta::Class;
use XXX::Keyword::Work;
Moose::Exporter->setup_import_methods();
sub init_meta {
my ($me, %options) = @_;
my $for = $options{for_class};
XXX::Keyword::Work->install_methodhandler(into => $for,);
Moose::Util::MetaRole::apply_metaclass_roles(
for_class => $for,
metaclass_roles => ['XXX::Meta::Class'],
);
}
1;
```
The `init_meta` method is provided by Moose: (from the POD)
> The `init_meta` method sets up the metaclass object for the class specified by `for_class`. This method injects a a meta accessor into the class so you can get at this object. It also sets the class's superclass to base_class, with Moose::Object as the default.
So I inject into the class that will use XXX::Meta a new metaclass, XXX::Meta::Class.
Let's take a look to XXX::Meta::Class:
```perl
package XXX::Meta::Class;
use Moose::Role;
use Moose::Meta::Class;
use MooseX::Types::Moose qw(Str ArrayRef ClassName Object);
has work_metaclass => (
is => 'ro',
isa => Object,
builder => '_build_metaclass',
lazy => 1,
);
has 'local_work' => (
traits => ['Array'],
is => 'ro',
isa => ArrayRef [Str],
required => 1,
default => sub { [] },
auto_deref => 1,
handles => {'_add_work' => 'push',}
);
sub _build_metaclass {
my $self = shift;
return Moose::Meta::Class->create_anon_class(
superclasses => [$self->method_metaclass],
cache => 1,
);
}
sub add_local_method {
my ($self, $method, $name, $code) = @_;
my $method_name = $method . "_" . $name;
my $body = $self->work_metaclass->name->wrap(
$code,
original_body => $code,
name => $method_name,
package_name => $self->name,
);
my $method_add = "_add_" . $method;
$self->add_method($method_name, $body);
$self->$method_add($method_name);
}
1;
```
Here I add to the `->meta` provided by Moose `local_work`, which is an array that contains all my `work` methods. So each time I do something like
```perl
work foo {};
work bar {};
```
in my worker, I add this method to **->meta->local_work**.
And the class for our keyword work:
```perl
package XXX::Keyword::Work;
use strict;
use warnings;
use Devel::Declare ();
use Sub::Name;
use base 'Devel::Declare::Context::Simple';
sub install_methodhandler {
my $class = shift;
my %args = @_;
{
no strict 'refs';
*{$args{into} . '::work'} = sub (&) { };
}
my $ctx = $class->new(%args);
Devel::Declare->setup_for(
$args{into},
{ work => {
const => sub { $ctx->parser(@_) }
},
}
);
}
sub parser {
my $self = shift;
$self->init(@_);
$self->skip_declarator;
my $name = $self->strip_name;
$self->strip_proto;
$self->strip_attrs;
my $inject = $self->scope_injector_call();
$self->inject_if_block(
$inject . " my (\$self, \$content, \$job) = \@_; ");
my $pack = Devel::Declare::get_curstash_name;
Devel::Declare::shadow_sub(
"${pack}::work",
sub (&) {
my $work_method = shift;
$pack->meta->add_local_method('work', $name, $work_method);
}
);
return;
}
1;
```
The `install_methodhandler` add the `work` keyword, with a block of code. This code is sent to the parser, that will add more sugar. With the inject_if_block, I inject the following line
```perl
my ($self, $context, $job) = @_;
```
as this will always be my 3 arguments for a work method.
Now, for each new worker, I write something like this:
```perl
package MyWorker;
use Moose;
extends 'XXX::Worker';
use XXX::Meta;
work foo {};
```
The next step is too find the best way to reduce the first four lines to two.
(some of this code is ripped from other modules that use Devel::Declare. The best way to learn what you can do with this module is to read code from other modules that use it)