MooseX::Role::Parameterized::Tutorial - why and how |
MooseX::Role::Parameterized::Tutorial - why and how
version 1.08
Roles are composable units of behavior. They are useful for factoring out functionality common to many classes from any part of your class hierarchy. See the Moose::Cookbook::Roles::Comparable_CodeReuse manpage for an introduction to the Moose::Role manpage.
While combining roles affords you a great deal of flexibility, individual roles
have very little in the way of configurability. Core Moose provides -alias
for renaming methods and -excludes
for ignoring methods. These options are
primarily for resolving role conflicts. Depending on how much of a purist you are,
these options are solely for resolving role conflicts. See
the Moose::Cookbook::Roles::Restartable_AdvancedComposition manpage for more about -alias
and -excludes
.
Because roles serve many different masters, they usually provide only the least
common denominator of functionality. To empower roles further, more
configurability than -alias
and -excludes
is required. Perhaps your role
needs to know which method to call when it is done processing. Or what default
value to use for its url
attribute.
Parameterized roles offer a solution to these (and other) kinds of problems.
with
The syntax of a class consuming a parameterized role has not changed
from the standard with
. You pass in parameters just like you
pass in -alias
and -excludes
to ordinary roles (though your
custom parameters do not get hyphens, since these are not core Moose
composition parameters):
with 'MyRole::InstrumentMethod' => { method_name => 'dbh_do', log_to => 'query.log', };
You can still combine parameterized roles. You just need to specify parameters immediately after the role they belong to:
with ( 'My::Parameterized::Role' => { needs_better_example => 1, }, 'My::Other::Role', );
We, like Moose itself, use the Data::OptList manpage to make sure that a list of role names and associated parameters is handled correctly.
parameter
Inside your parameterized role, you specify a set of parameters. This is
exactly like specifying the attributes of a class. Instead of Moose/has you
use the keyword parameter
, but your parameters can use any options to
has
.
parameter 'delegation' => ( isa => 'HashRef|ArrayRef|RegexpRef', predicate => 'has_delegation', );
You do have to declare what parameters you accept, just like you have to declare what attributes you accept for regular Moose objects.
One departure from has
is that we create a reader accessor for you by
default. In other words, we assume is => 'ro'
. We create this reader for
convenience because generally the parameterized role is the only consumer of
the parameters object, so data hiding is not as important than in the general
case of Moose/has. If you do not want an accessor, you can use
is => 'bare'
.
role
role
takes a block of code that will be used to generate your role with its
parameters bound. Here is where you declare components that depend on
parameters. You can declare attributes, methods, modifiers, etc. The first
argument to the role
is an object containing the parameters specified by
with
. You can access the parameters just like regular attributes on that
object.
Each time you compose this parameterized role, the role {}
block will be
executed. It will receive a new parameter object and produce an entirely new
role. That's the whole point, after all.
Due to limitations inherent in Perl, you must declare methods with
method name => sub { ... }
instead of the usual sub name { ... }
.
Your methods may, of course, close over the parameter object. This means that
your methods may use parameters however they wish!
Ideally these will become fully-explained examples in something resembling the Moose::Cookbook manpage. But for now, only a brain dump.
parameter traits => ( isa => 'ArrayRef', default => sub { [] }, );
parameter type => ( isa => 'Str', default => 'Any', );
role { my $p = shift;
has action => ( traits => $p->traits, isa => $p->type, ... ); }
parameter instrument_method => ( isa => 'Str', required => 1, );
role { my $p = shift; around $p->instrument_method => sub { ... }; }
parameter save_intermediate => ( isa => 'Bool', default => 0, );
role { my $p = shift; method process => sub { ... if ($p->save_intermediate) { ... } ... }; }
parameter format => ( isa => (enum ['Storable', 'YAML', 'JSON']), default => 'Storable', );
role { my $p = shift; if ($p->format eq 'Storable') { method freeze => \&Storable::freeze; method thaw => \&Storable::thaw; } elsif ($p->format eq 'YAML') { method freeze => \&YAML::Dump; method thaw => \&YAML::Load; } ... }
role { my $p = shift; my %args = @_; my $consumer = $args{consumer};
$consumer->find_attribute_by_name('stack') or confess "You must have a 'stack' attribute";
my $push = $consumer->find_method_by_name('push') or confess "You must have a 'push' method";
my $params = $push->parsed_signature->positional_params->params; @$params == 1 or confess "Your push method must take a single parameter";
$params->[0]->sigil eq '$' or confess "Your push parameter must be a scalar";
... }
Shawn M Moore <code@sartak.org>
This software is copyright (c) 2008 by Shawn M Moore.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
MooseX::Role::Parameterized::Tutorial - why and how |