summaryrefslogtreecommitdiff
path: root/posts/2009-11-09-modules-i-like-devel-declare.org
diff options
context:
space:
mode:
authorFranck Cuny <franckcuny@gmail.com>2016-08-04 11:45:44 -0700
committerFranck Cuny <franckcuny@gmail.com>2016-08-04 11:45:44 -0700
commit585b48b6a605cb71ef99dd767880e1b7ee5bf24e (patch)
treec65377350d12bd1e62e0bdd58458c1044541c27b /posts/2009-11-09-modules-i-like-devel-declare.org
parentUse Bullet list for the index. (diff)
parentMass convert all posts from markdown to org. (diff)
downloadlumberjaph-585b48b6a605cb71ef99dd767880e1b7ee5bf24e.tar.gz
Merge branch 'convert-to-org'
Diffstat (limited to 'posts/2009-11-09-modules-i-like-devel-declare.org')
-rw-r--r--posts/2009-11-09-modules-i-like-devel-declare.org265
1 files changed, 265 insertions, 0 deletions
diff --git a/posts/2009-11-09-modules-i-like-devel-declare.org b/posts/2009-11-09-modules-i-like-devel-declare.org
new file mode 100644
index 0000000..facdc81
--- /dev/null
+++ b/posts/2009-11-09-modules-i-like-devel-declare.org
@@ -0,0 +1,265 @@
+For [[http://linkfluence.net/][$work]], 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:
+
+#+BEGIN_SRC 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;
+ }
+#+END_SRC
+
+and the worker look like this
+
+#+BEGIN_SRC perl
+ package MyWorker;
+ use Moose;
+ extends 'XXX::Worker';
+
+ sub foo {
+ my ($self, $context, $job) = @_;
+
+ # do something
+ $self->job_success();
+ }
+#+END_SRC
+
+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
+[[http://search.cpan.org/perldoc?Devel::Declare][Devel::Declare]].
+
+The syntax I want for my worker is this one:
+
+#+BEGIN_SRC 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");
+ };
+#+END_SRC
+
+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:
+
+#+BEGIN_SRC 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;
+#+END_SRC
+
+The =init_meta= method is provided by Moose: (from the POD)
+
+#+BEGIN_QUOTE
+ 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.
+#+END_QUOTE
+
+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:
+
+#+BEGIN_SRC 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;
+#+END_SRC
+
+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
+
+#+BEGIN_SRC perl
+ work foo {};
+
+ work bar {};
+#+END_SRC
+
+in my worker, I add this method to *->meta->local\_work*.
+
+And the class for our keyword work:
+
+#+BEGIN_SRC 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;
+#+END_SRC
+
+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
+
+#+BEGIN_SRC perl
+ my ($self, $context, $job) = @_;
+#+END_SRC
+
+as this will always be my 3 arguments for a work method.
+
+Now, for each new worker, I write something like this:
+
+#+BEGIN_SRC perl
+ package MyWorker;
+ use Moose;
+ extends 'XXX::Worker';
+ use XXX::Meta;
+
+ work foo {};
+#+END_SRC
+
+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)