diff options
| author | Franck Cuny <franckcuny@gmail.com> | 2016-07-02 20:06:31 -0700 |
|---|---|---|
| committer | Franck Cuny <franckcuny@gmail.com> | 2016-07-02 20:06:31 -0700 |
| commit | 4b8e43f75b394a4e6169884fbfb4c606865c6a22 (patch) | |
| tree | 48cae6b8e8f9b68cae29676d8a15cb3ddbfcccda /content/post/2009-11-09-modules-i-like-devel-declare.md | |
| parent | Stop using Jekyll. (diff) | |
| download | lumberjaph-4b8e43f75b394a4e6169884fbfb4c606865c6a22.tar.gz | |
Import migration from Jekyll to Hugo.
All the posts were converted, and the layout is created. This looks like
it works just fine.
Diffstat (limited to 'content/post/2009-11-09-modules-i-like-devel-declare.md')
| -rw-r--r-- | content/post/2009-11-09-modules-i-like-devel-declare.md | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/content/post/2009-11-09-modules-i-like-devel-declare.md b/content/post/2009-11-09-modules-i-like-devel-declare.md new file mode 100644 index 0000000..fdae319 --- /dev/null +++ b/content/post/2009-11-09-modules-i-like-devel-declare.md @@ -0,0 +1,248 @@ +--- +date: 2009-11-09T00:00:00Z +summary: In which I share my enthusiasm for Devel::Declare. +title: Modules I like Devel::Declare +--- + +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) |
