File Coverage

blib/lib/MooseX/ComposedBehavior.pm
Criterion Covered Total %
statement 34 34 100.0
branch 11 14 78.5
condition n/a
subroutine 9 9 100.0
pod n/a
total 54 57 94.7


line stmt bran cond sub pod time code
1 5     5   2837932 use strict;
  5         14  
  5         171  
2 5     5   33 use warnings;
  5         8  
  5         299  
3             package MooseX::ComposedBehavior;
4             {
5             $MooseX::ComposedBehavior::VERSION = '0.004';
6             }
7             # ABSTRACT: implement custom strategies for composing units of code
8              
9              
10 5     5   3002 use MooseX::ComposedBehavior::Guts;
  5         16  
  5         297  
11              
12 5         82 use Sub::Exporter -setup => {
13             groups => [ compose => \'_build_composed_behavior' ],
14 5     5   108 };
  5         9  
15              
16             my $i = 0;
17              
18             sub _build_composed_behavior {
19 8     8   4446 my ($self, $name, $arg, $col) = @_;
20              
21 8         14 my %sub;
22              
23 8         18 my $sugar_name = $arg->{sugar_name};
24 8         30 my $stub_name = 'MooseX_ComposedBehavior_' . $i++ . "_$sugar_name";
25              
26 8 100       58 my $role = MooseX::ComposedBehavior::Guts->meta->generate_role(
    50          
    50          
    100          
27             ($arg->{role_name} ? (package => $arg->{role_name}) : ()),
28             parameters => {
29             stub_method_name => $stub_name,
30             compositor => $arg->{compositor},
31             method_name => $arg->{method_name},
32              
33             (defined $arg->{also_compose}
34             ? (also_compose => $arg->{also_compose})
35             : ()),
36              
37             (defined $arg->{method_order}
38             ? (method_order => $arg->{method_order})
39             : ()),
40              
41             (defined $arg->{context} ? (context => $arg->{context}) : ()),
42             },
43             );
44              
45             my $import = Sub::Exporter::build_exporter({
46             groups => [ default => [ $sugar_name ] ],
47             exports => {
48             $sugar_name => sub {
49 24     24   3263 my ($self, $name, $arg, $col) = @_;
50 24         85 my $target = $col->{INIT}{target};
51             return sub (&) {
52 25     25   265757 my ($code) = shift;
53              
54             Moose::Util::add_method_modifier(
55             $target->meta,
56             'around',
57             [
58             $stub_name,
59             sub {
60 41     41   242 my ($orig, $self, $arg, $col) = @_;
61              
62 41 100       141 my @array = (wantarray
63             ? $self->$code(@$arg)
64             : scalar $self->$code(@$arg)
65             );
66              
67 41 100       252 push @$col, wantarray ? \@array : $array[0];
68 41         126 $self->$orig($arg, $col);
69             },
70 25         163 ],
71             );
72             }
73 24         197 },
74             },
75             collectors => {
76             INIT => sub {
77 24     24   87232 my $target = $_[1]{into};
78 24         95 $_[0] = { target => $target };
79              
80             # Applying roles to the target fails mysteriously if it is not (yet)
81             # something to which roles can be applied, for example if the "use
82             # Moose" decl appears after "use MooseX::ComposedBehavior" [MJD]
83 24 50       115 Moose::Util::find_meta($target)
84             or Carp::confess(__PACKAGE__ .
85             ": target package '$target' is not a Moose class");
86 24         379 Moose::Util::apply_all_roles($target, $role);
87 24         38490 return 1;
88             },
89             },
90 8         1722 });
91              
92 8         2077 $sub{import} = $import;
93              
94 8         46 return \%sub;
95             }
96              
97             1;
98              
99             __END__
100              
101             =pod
102              
103             =head1 NAME
104              
105             MooseX::ComposedBehavior - implement custom strategies for composing units of code
106              
107             =head1 VERSION
108              
109             version 0.004
110              
111             =head1 OVERVIEW
112              
113             First, B<a warning>: MooseX::ComposedBehavior is a weird and powerful tool
114             meant to be used only I<well> after traditional means of composition have
115             failed. Almost everything most programs will need can be represented with
116             Moose's normal mechanisms for roles, classes, and method modifiers.
117             MooseX::ComposedBehavior addresses edge cases.
118              
119             Second, B<another warning>: the API for MooseX::ComposedBehavior is not quite
120             stable, and may yet change. More likely, though, the underlying implementation
121             may change. The current implementation is something of a hack, and should be
122             replaced by a more robust one. When that happens, if your code is not sticking
123             strictly to the MooseX::ComposedBehavior API, you will probably have all kinds
124             of weird problems.
125              
126             =head1 SYNOPSIS
127              
128             First, you describe your composed behavior, say in the package "TagProvider":
129              
130             package TagProvider;
131             use strict;
132              
133             use MooseX::ComposedBehavior -compose => {
134             method_name => 'tags',
135             sugar_name => 'add_tags',
136             context => 'list',
137             compositor => sub {
138             my ($self, $results) = @_;
139             return map { @$_ } @$results if wantarray;
140             },
141             };
142              
143             Now, any class or role can C<use TagProvider> to declare that it's going to
144             contribute to a collection of tags. Any class that has used C<TagProvider>
145             will have a C<tags> method, named by the C<method_name> argument. When it's
146             called, code registered the class's constituent parts will be called. For
147             example, consider this example:
148              
149             {
150             package Foo;
151             use Moose::Role;
152             use TagProvider;
153             add_tags { qw(foo baz) };
154             }
155              
156             {
157             package Bar;
158             use Moose::Role;
159             use t::TagProvider;
160             add_tags { qw(bar quux) };
161             }
162              
163             {
164             package Thing;
165             use Moose;
166             use t::TagProvider;
167             with qw(Foo Bar);
168             add_tags { qw(bingo) };
169             }
170              
171             Now, when you say:
172              
173             my $thing = Thing->new;
174             my @tags = $thing->tags;
175              
176             ...each of the C<add_tags> code blocks above is called. The result of each
177             block is gathered and an arrayref of all the results is passed to the
178             C<compositor> routine. The one we defined above is very simple, and just
179             concatenates all the results together.
180              
181             C<@tags> will contain, in no particular order: foo, bar, baz, quux, and bingo
182              
183             Result composition can be much more complex, and the context in which the
184             registered blocks are called can be controlled. The options for composed
185             behavior are described below.
186              
187             =head1 HOW TO USE IT
188              
189             =over 4
190              
191             =item 1
192              
193             make a helper module, like the "TagProvider" one above
194              
195             =item 2
196              
197             C<use> the helper in every relevant role or class
198              
199             =item 3
200              
201             write blocks using the "sugar" function
202              
203             =item 4
204              
205             call the method on instances as needed
206              
207             =item 5
208              
209             you're done!
210              
211             =back
212              
213             There isn't much to using it beyond knowing how to write the actual behavior
214             compositor (or "helper module") that you want. Helper modules will probably
215             always be very short: package declaration, C<use strict>,
216             MooseX::ComposedBehavior invocation, and nothing more. Everything important
217             goes in the arguments to MooseX::ComposedBehavior's import routine:
218              
219             package MyHelper;
220             use strict;
221              
222             use MooseX::ComposedBehavior -compose => {
223             ... important stuff goes here ...
224             };
225              
226             1;
227              
228             =head2 Options to MooseX::ComposedBehavior
229              
230             =over 4
231              
232             =item C<method_name>
233              
234             This is the name of the method that you'll call to get composed results. When
235             this method is called, all the registered behavior is run, the results
236             gathered, and those results passed to the compositor (described below).
237              
238             =item C<sugar_name>
239              
240             This is the of the sugar to export into packages using the helper module. It
241             should be called like this (assuming the C<sugar_name> is C<add_behavior>):
242              
243             add_behavior { ...the behavior... ; return $value };
244              
245             When this block is invoked, it will be passed the invocant (the class or
246             instance) followed by all the arguments passed to the main method -- that is,
247             the method named by C<method_name>.
248              
249             =item C<context>
250              
251             This parameter forces a specific calling context on the registered blocks of
252             behavior. It can be either "scalar" or "list" or may be omitted. The blocks
253             registered by the sugar function will always be called in the given context.
254             If no context is given, they will be called in the same context that the main
255             method was called.
256              
257             The C<context> option does I<not> affect the context in which the compositor is
258             called. It is always called in the same context as the main method.
259              
260             Void context is propagated as scalar context. B<This may change in the
261             future> to support void context per se.
262              
263             =item C<compositor>
264              
265             The compositor is a coderef that gets all the results of registered behavior
266             (and C<also_compose>, below) and combines them into a final result, which will
267             be returned from the main method.
268              
269             It is passed the invocant, followed by an arrayref of block results. The
270             block results are in an undefined order. If the blocks were called in scalar
271             context, each block's result is the returned scalar. If the blocks were called
272             in list context, each block's result is an arrayref containing the returned
273             list.
274              
275             The compositor is I<always> called in the same context as the main method, even
276             if the behavior blocks were forced into a different context.
277              
278             =item C<also_compose>
279              
280             This parameter is a coderef or method name, or an arrayref of coderefs and/or
281             method names. These will be called along with the rest of the registered
282             behavior, in the same context, and their results will be composed like any
283             other results. It would be possible to simply write this:
284              
285             add_behavior {
286             my $self = shift;
287             $self->some_method;
288             };
289              
290             ...but if this was somehow composed more than once (by repeating a role
291             application, for example) you would get the results of C<some_method> more than
292             once. By putting the method into the C<also_compose> option, you are
293             guaranteed that it will run only once.
294              
295             =item C<method_order>
296              
297             By default, registered behaviors are called on the most derived class and its
298             roles, first. That is: the class closest to the class of the method invocant,
299             then upward toward superclasses. This is how the C<DEMOLISH> methods in
300             L<Moose::Object> work.
301              
302             If C<method_order> is provided, and is "reverse" then the methods are called in
303             reverse order: base class first, followed by derived classes. This is how the
304             C<BUILD> methods in Moose::Object work.
305              
306             =back
307              
308             =head1 AUTHOR
309              
310             Ricardo Signes <rjbs@cpan.org>
311              
312             =head1 COPYRIGHT AND LICENSE
313              
314             This software is copyright (c) 2013 by Ricardo Signes.
315              
316             This is free software; you can redistribute it and/or modify it under
317             the same terms as the Perl 5 programming language system itself.
318              
319             =cut