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   2400042 use strict;
  5         48  
  5         129  
2 5     5   25 use warnings;
  5         10  
  5         377  
3             package MooseX::ComposedBehavior 0.005;
4             # ABSTRACT: implement custom strategies for composing units of code
5              
6             #pod =begin :prelude
7             #pod
8             #pod =head1 OVERVIEW
9             #pod
10             #pod First, B<a warning>: MooseX::ComposedBehavior is a weird and powerful tool
11             #pod meant to be used only I<well> after traditional means of composition have
12             #pod failed. Almost everything most programs will need can be represented with
13             #pod Moose's normal mechanisms for roles, classes, and method modifiers.
14             #pod MooseX::ComposedBehavior addresses edge cases.
15             #pod
16             #pod Second, B<another warning>: the API for MooseX::ComposedBehavior is not quite
17             #pod stable, and may yet change. More likely, though, the underlying implementation
18             #pod may change. The current implementation is something of a hack, and should be
19             #pod replaced by a more robust one. When that happens, if your code is not sticking
20             #pod strictly to the MooseX::ComposedBehavior API, you will probably have all kinds
21             #pod of weird problems.
22             #pod
23             #pod =end :prelude
24             #pod
25             #pod =head1 SYNOPSIS
26             #pod
27             #pod First, you describe your composed behavior, say in the package "TagProvider":
28             #pod
29             #pod package TagProvider;
30             #pod use strict;
31             #pod
32             #pod use MooseX::ComposedBehavior -compose => {
33             #pod method_name => 'tags',
34             #pod sugar_name => 'add_tags',
35             #pod context => 'list',
36             #pod compositor => sub {
37             #pod my ($self, $results) = @_;
38             #pod return map { @$_ } @$results if wantarray;
39             #pod },
40             #pod };
41             #pod
42             #pod Now, any class or role can C<use TagProvider> to declare that it's going to
43             #pod contribute to a collection of tags. Any class that has used C<TagProvider>
44             #pod will have a C<tags> method, named by the C<method_name> argument. When it's
45             #pod called, code registered the class's constituent parts will be called. For
46             #pod example, consider this example:
47             #pod
48             #pod {
49             #pod package Foo;
50             #pod use Moose::Role;
51             #pod use TagProvider;
52             #pod add_tags { qw(foo baz) };
53             #pod }
54             #pod
55             #pod {
56             #pod package Bar;
57             #pod use Moose::Role;
58             #pod use t::TagProvider;
59             #pod add_tags { qw(bar quux) };
60             #pod }
61             #pod
62             #pod {
63             #pod package Thing;
64             #pod use Moose;
65             #pod use t::TagProvider;
66             #pod with qw(Foo Bar);
67             #pod add_tags { qw(bingo) };
68             #pod }
69             #pod
70             #pod Now, when you say:
71             #pod
72             #pod my $thing = Thing->new;
73             #pod my @tags = $thing->tags;
74             #pod
75             #pod ...each of the C<add_tags> code blocks above is called. The result of each
76             #pod block is gathered and an arrayref of all the results is passed to the
77             #pod C<compositor> routine. The one we defined above is very simple, and just
78             #pod concatenates all the results together.
79             #pod
80             #pod C<@tags> will contain, in no particular order: foo, bar, baz, quux, and bingo
81             #pod
82             #pod Result composition can be much more complex, and the context in which the
83             #pod registered blocks are called can be controlled. The options for composed
84             #pod behavior are described below.
85             #pod
86             #pod =head1 HOW TO USE IT
87             #pod
88             #pod =for :list
89             #pod 1. make a helper module, like the "TagProvider" one above
90             #pod 2. C<use> the helper in every relevant role or class
91             #pod 3. write blocks using the "sugar" function
92             #pod 4. call the method on instances as needed
93             #pod 5. you're done!
94             #pod
95             #pod There isn't much to using it beyond knowing how to write the actual behavior
96             #pod compositor (or "helper module") that you want. Helper modules will probably
97             #pod always be very short: package declaration, C<use strict>,
98             #pod MooseX::ComposedBehavior invocation, and nothing more. Everything important
99             #pod goes in the arguments to MooseX::ComposedBehavior's import routine:
100             #pod
101             #pod package MyHelper;
102             #pod use strict;
103             #pod
104             #pod use MooseX::ComposedBehavior -compose => {
105             #pod ... important stuff goes here ...
106             #pod };
107             #pod
108             #pod 1;
109             #pod
110             #pod =head2 Options to MooseX::ComposedBehavior
111             #pod
112             #pod =begin :list
113             #pod
114             #pod = C<method_name>
115             #pod
116             #pod This is the name of the method that you'll call to get composed results. When
117             #pod this method is called, all the registered behavior is run, the results
118             #pod gathered, and those results passed to the compositor (described below).
119             #pod
120             #pod = C<sugar_name>
121             #pod
122             #pod This is the of the sugar to export into packages using the helper module. It
123             #pod should be called like this (assuming the C<sugar_name> is C<add_behavior>):
124             #pod
125             #pod add_behavior { ...the behavior... ; return $value };
126             #pod
127             #pod When this block is invoked, it will be passed the invocant (the class or
128             #pod instance) followed by all the arguments passed to the main method -- that is,
129             #pod the method named by C<method_name>.
130             #pod
131             #pod = C<context>
132             #pod
133             #pod This parameter forces a specific calling context on the registered blocks of
134             #pod behavior. It can be either "scalar" or "list" or may be omitted. The blocks
135             #pod registered by the sugar function will always be called in the given context.
136             #pod If no context is given, they will be called in the same context that the main
137             #pod method was called.
138             #pod
139             #pod The C<context> option does I<not> affect the context in which the compositor is
140             #pod called. It is always called in the same context as the main method.
141             #pod
142             #pod Void context is propagated as scalar context. B<This may change in the
143             #pod future> to support void context per se.
144             #pod
145             #pod = C<compositor>
146             #pod
147             #pod The compositor is a coderef that gets all the results of registered behavior
148             #pod (and C<also_compose>, below) and combines them into a final result, which will
149             #pod be returned from the main method.
150             #pod
151             #pod It is passed the invocant, followed by an arrayref of block results. The
152             #pod block results are in an undefined order. If the blocks were called in scalar
153             #pod context, each block's result is the returned scalar. If the blocks were called
154             #pod in list context, each block's result is an arrayref containing the returned
155             #pod list.
156             #pod
157             #pod The compositor is I<always> called in the same context as the main method, even
158             #pod if the behavior blocks were forced into a different context.
159             #pod
160             #pod = C<also_compose>
161             #pod
162             #pod This parameter is a coderef or method name, or an arrayref of coderefs and/or
163             #pod method names. These will be called along with the rest of the registered
164             #pod behavior, in the same context, and their results will be composed like any
165             #pod other results. It would be possible to simply write this:
166             #pod
167             #pod add_behavior {
168             #pod my $self = shift;
169             #pod $self->some_method;
170             #pod };
171             #pod
172             #pod ...but if this was somehow composed more than once (by repeating a role
173             #pod application, for example) you would get the results of C<some_method> more than
174             #pod once. By putting the method into the C<also_compose> option, you are
175             #pod guaranteed that it will run only once.
176             #pod
177             #pod = C<method_order>
178             #pod
179             #pod By default, registered behaviors are called on the most derived class and its
180             #pod roles, first. That is: the class closest to the class of the method invocant,
181             #pod then upward toward superclasses. This is how the C<DEMOLISH> methods in
182             #pod L<Moose::Object> work.
183             #pod
184             #pod If C<method_order> is provided, and is "reverse" then the methods are called in
185             #pod reverse order: base class first, followed by derived classes. This is how the
186             #pod C<BUILD> methods in Moose::Object work.
187             #pod
188             #pod =end :list
189             #pod
190             #pod =cut
191              
192 5     5   1912 use MooseX::ComposedBehavior::Guts;
  5         19  
  5         282  
193              
194 5         62 use Sub::Exporter -setup => {
195             groups => [ compose => \'_build_composed_behavior' ],
196 5     5   41 };
  5         11  
197              
198             my $i = 0;
199              
200             sub _build_composed_behavior {
201 8     8   2766 my ($self, $name, $arg, $col) = @_;
202              
203 8         15 my %sub;
204              
205 8         17 my $sugar_name = $arg->{sugar_name};
206 8         26 my $stub_name = 'MooseX_ComposedBehavior_' . $i++ . "_$sugar_name";
207              
208             my $role = MooseX::ComposedBehavior::Guts->meta->generate_role(
209             ($arg->{role_name} ? (package => $arg->{role_name}) : ()),
210             parameters => {
211             stub_method_name => $stub_name,
212             compositor => $arg->{compositor},
213             method_name => $arg->{method_name},
214              
215             (defined $arg->{also_compose}
216             ? (also_compose => $arg->{also_compose})
217             : ()),
218              
219             (defined $arg->{method_order}
220             ? (method_order => $arg->{method_order})
221             : ()),
222              
223 8 100       48 (defined $arg->{context} ? (context => $arg->{context}) : ()),
    50          
    50          
    100          
224             },
225             );
226              
227             my $import = Sub::Exporter::build_exporter({
228             groups => [ default => [ $sugar_name ] ],
229             exports => {
230             $sugar_name => sub {
231 24     24   3027 my ($self, $name, $arg, $col) = @_;
232 24         57 my $target = $col->{INIT}{target};
233             return sub (&) {
234 25     25   192214 my ($code) = shift;
235              
236             Moose::Util::add_method_modifier(
237             $target->meta,
238             'around',
239             [
240             $stub_name,
241             sub {
242 41     41   359 my ($orig, $self, $arg, $col) = @_;
243              
244 41 100       125 my @array = (wantarray
245             ? $self->$code(@$arg)
246             : scalar $self->$code(@$arg)
247             );
248              
249 41 100       192 push @$col, wantarray ? \@array : $array[0];
250 41         90 $self->$orig($arg, $col);
251             },
252 25         125 ],
253             );
254             }
255 24         124 },
256             },
257             collectors => {
258             INIT => sub {
259 24     24   71235 my $target = $_[1]{into};
260 24         67 $_[0] = { target => $target };
261              
262             # Applying roles to the target fails mysteriously if it is not (yet)
263             # something to which roles can be applied, for example if the "use
264             # Moose" decl appears after "use MooseX::ComposedBehavior" [MJD]
265 24 50       81 Moose::Util::find_meta($target)
266             or Carp::confess(__PACKAGE__ .
267             ": target package '$target' is not a Moose class");
268 24         300 Moose::Util::apply_all_roles($target, $role);
269 24         31804 return 1;
270             },
271             },
272 8         1435 });
273              
274 8         2226 $sub{import} = $import;
275              
276 8         30 return \%sub;
277             }
278              
279             1;
280              
281             __END__
282              
283             =pod
284              
285             =encoding UTF-8
286              
287             =head1 NAME
288              
289             MooseX::ComposedBehavior - implement custom strategies for composing units of code
290              
291             =head1 VERSION
292              
293             version 0.005
294              
295             =head1 OVERVIEW
296              
297             First, B<a warning>: MooseX::ComposedBehavior is a weird and powerful tool
298             meant to be used only I<well> after traditional means of composition have
299             failed. Almost everything most programs will need can be represented with
300             Moose's normal mechanisms for roles, classes, and method modifiers.
301             MooseX::ComposedBehavior addresses edge cases.
302              
303             Second, B<another warning>: the API for MooseX::ComposedBehavior is not quite
304             stable, and may yet change. More likely, though, the underlying implementation
305             may change. The current implementation is something of a hack, and should be
306             replaced by a more robust one. When that happens, if your code is not sticking
307             strictly to the MooseX::ComposedBehavior API, you will probably have all kinds
308             of weird problems.
309              
310             =head1 SYNOPSIS
311              
312             First, you describe your composed behavior, say in the package "TagProvider":
313              
314             package TagProvider;
315             use strict;
316              
317             use MooseX::ComposedBehavior -compose => {
318             method_name => 'tags',
319             sugar_name => 'add_tags',
320             context => 'list',
321             compositor => sub {
322             my ($self, $results) = @_;
323             return map { @$_ } @$results if wantarray;
324             },
325             };
326              
327             Now, any class or role can C<use TagProvider> to declare that it's going to
328             contribute to a collection of tags. Any class that has used C<TagProvider>
329             will have a C<tags> method, named by the C<method_name> argument. When it's
330             called, code registered the class's constituent parts will be called. For
331             example, consider this example:
332              
333             {
334             package Foo;
335             use Moose::Role;
336             use TagProvider;
337             add_tags { qw(foo baz) };
338             }
339              
340             {
341             package Bar;
342             use Moose::Role;
343             use t::TagProvider;
344             add_tags { qw(bar quux) };
345             }
346              
347             {
348             package Thing;
349             use Moose;
350             use t::TagProvider;
351             with qw(Foo Bar);
352             add_tags { qw(bingo) };
353             }
354              
355             Now, when you say:
356              
357             my $thing = Thing->new;
358             my @tags = $thing->tags;
359              
360             ...each of the C<add_tags> code blocks above is called. The result of each
361             block is gathered and an arrayref of all the results is passed to the
362             C<compositor> routine. The one we defined above is very simple, and just
363             concatenates all the results together.
364              
365             C<@tags> will contain, in no particular order: foo, bar, baz, quux, and bingo
366              
367             Result composition can be much more complex, and the context in which the
368             registered blocks are called can be controlled. The options for composed
369             behavior are described below.
370              
371             =head1 PERL VERSION
372              
373             This library should run on perls released even a long time ago. It should work
374             on any version of perl released in the last five years.
375              
376             Although it may work on older versions of perl, no guarantee is made that the
377             minimum required version will not be increased. The version may be increased
378             for any reason, and there is no promise that patches will be accepted to lower
379             the minimum required perl.
380              
381             =head1 HOW TO USE IT
382              
383             =over 4
384              
385             =item 1
386              
387             make a helper module, like the "TagProvider" one above
388              
389             =item 2
390              
391             C<use> the helper in every relevant role or class
392              
393             =item 3
394              
395             write blocks using the "sugar" function
396              
397             =item 4
398              
399             call the method on instances as needed
400              
401             =item 5
402              
403             you're done!
404              
405             =back
406              
407             There isn't much to using it beyond knowing how to write the actual behavior
408             compositor (or "helper module") that you want. Helper modules will probably
409             always be very short: package declaration, C<use strict>,
410             MooseX::ComposedBehavior invocation, and nothing more. Everything important
411             goes in the arguments to MooseX::ComposedBehavior's import routine:
412              
413             package MyHelper;
414             use strict;
415              
416             use MooseX::ComposedBehavior -compose => {
417             ... important stuff goes here ...
418             };
419              
420             1;
421              
422             =head2 Options to MooseX::ComposedBehavior
423              
424             =over 4
425              
426             =item C<method_name>
427              
428             This is the name of the method that you'll call to get composed results. When
429             this method is called, all the registered behavior is run, the results
430             gathered, and those results passed to the compositor (described below).
431              
432             =item C<sugar_name>
433              
434             This is the of the sugar to export into packages using the helper module. It
435             should be called like this (assuming the C<sugar_name> is C<add_behavior>):
436              
437             add_behavior { ...the behavior... ; return $value };
438              
439             When this block is invoked, it will be passed the invocant (the class or
440             instance) followed by all the arguments passed to the main method -- that is,
441             the method named by C<method_name>.
442              
443             =item C<context>
444              
445             This parameter forces a specific calling context on the registered blocks of
446             behavior. It can be either "scalar" or "list" or may be omitted. The blocks
447             registered by the sugar function will always be called in the given context.
448             If no context is given, they will be called in the same context that the main
449             method was called.
450              
451             The C<context> option does I<not> affect the context in which the compositor is
452             called. It is always called in the same context as the main method.
453              
454             Void context is propagated as scalar context. B<This may change in the
455             future> to support void context per se.
456              
457             =item C<compositor>
458              
459             The compositor is a coderef that gets all the results of registered behavior
460             (and C<also_compose>, below) and combines them into a final result, which will
461             be returned from the main method.
462              
463             It is passed the invocant, followed by an arrayref of block results. The
464             block results are in an undefined order. If the blocks were called in scalar
465             context, each block's result is the returned scalar. If the blocks were called
466             in list context, each block's result is an arrayref containing the returned
467             list.
468              
469             The compositor is I<always> called in the same context as the main method, even
470             if the behavior blocks were forced into a different context.
471              
472             =item C<also_compose>
473              
474             This parameter is a coderef or method name, or an arrayref of coderefs and/or
475             method names. These will be called along with the rest of the registered
476             behavior, in the same context, and their results will be composed like any
477             other results. It would be possible to simply write this:
478              
479             add_behavior {
480             my $self = shift;
481             $self->some_method;
482             };
483              
484             ...but if this was somehow composed more than once (by repeating a role
485             application, for example) you would get the results of C<some_method> more than
486             once. By putting the method into the C<also_compose> option, you are
487             guaranteed that it will run only once.
488              
489             =item C<method_order>
490              
491             By default, registered behaviors are called on the most derived class and its
492             roles, first. That is: the class closest to the class of the method invocant,
493             then upward toward superclasses. This is how the C<DEMOLISH> methods in
494             L<Moose::Object> work.
495              
496             If C<method_order> is provided, and is "reverse" then the methods are called in
497             reverse order: base class first, followed by derived classes. This is how the
498             C<BUILD> methods in Moose::Object work.
499              
500             =back
501              
502             =head1 AUTHOR
503              
504             Ricardo Signes <cpan@semiotic.systems>
505              
506             =head1 CONTRIBUTORS
507              
508             =for stopwords Mark Dominus Ricardo Signes
509              
510             =over 4
511              
512             =item *
513              
514             Mark Dominus <mjd@icgroup.com>
515              
516             =item *
517              
518             Ricardo Signes <rjbs@semiotic.systems>
519              
520             =back
521              
522             =head1 COPYRIGHT AND LICENSE
523              
524             This software is copyright (c) 2022 by Ricardo Signes.
525              
526             This is free software; you can redistribute it and/or modify it under
527             the same terms as the Perl 5 programming language system itself.
528              
529             =cut