File Coverage

blib/lib/MooseX/ErsatzMethod.pm
Criterion Covered Total %
statement 98 99 98.9
branch 9 12 75.0
condition 6 12 50.0
subroutine 27 27 100.0
pod 1 5 20.0
total 141 155 90.9


line stmt bran cond sub pod time code
1 5     5   1655469 use 5.008;
  5         19  
2 5     5   24 use strict;
  5         10  
  5         99  
3 5     5   23 use warnings;
  5         12  
  5         231  
4              
5             package MooseX::ErsatzMethod;
6              
7             BEGIN {
8 5     5   16 $MooseX::ErsatzMethod::AUTHORITY = 'cpan:TOBYINK';
9 5         189 $MooseX::ErsatzMethod::VERSION = '0.005';
10             }
11              
12             my %METAROLES;
13             BEGIN {
14 5     5   103 %METAROLES = (
15             role => [ 'MooseX::ErsatzMethod::Trait::Role' ],
16             application_to_class => [ 'MooseX::ErsatzMethod::Trait::ApplicationToClass' ],
17             application_to_role => [ 'MooseX::ErsatzMethod::Trait::ApplicationToRole' ],
18             application_to_instance => [ 'MooseX::ErsatzMethod::Trait::ApplicationToInstance' ],
19             )
20             };
21              
22 5     5   422 use Module::Runtime ();
  5         1665  
  5         74  
23 5     5   468 use Moose ();
  5         392386  
  5         139  
24 5     5   28 use Moose::Exporter;
  5         9  
  5         34  
25              
26             BEGIN {
27             package MooseX::ErsatzMethod::Meta::Method;
28 5     5   32684 our $AUTHORITY = 'cpan:TOBYINK';
29 5         11 our $VERSION = '0.005';
30 5     5   331 use Moose;
  5         38  
  5         31  
31 5         25 has code => (
32             is => 'ro',
33             isa => 'CodeRef',
34             required => 1,
35             );
36 5         1300 has name => (
37             is => 'ro',
38             isa => 'Str',
39             required => 1,
40             );
41 5         1075 has associated_role => (
42             is => 'ro',
43             isa => 'Object',
44             required => 0,
45             );
46             sub apply_to_class
47             {
48 13     13 0 48 my ($self, $class) = @_;
49 13 100       370 return if $class->find_method_by_name($self->name);
50 9         1586 $class->add_method($self->name, $self->code);
51             }
52 5   50     1155 $INC{ Module::Runtime::module_notional_filename(__PACKAGE__) } ||= __FILE__;
53             }
54              
55             BEGIN {
56             package MooseX::ErsatzMethod::Trait::Role;
57 5     5   27045 our $AUTHORITY = 'cpan:TOBYINK';
58 5         13 our $VERSION = '0.005';
59 5     5   1116 use Moose::Role;
  5         4447  
  5         39  
60 5         31 has ersatz_methods => (
61             traits => ['Hash'],
62             is => 'ro',
63             isa => 'HashRef[MooseX::ErsatzMethod::Meta::Method]',
64             lazy_build => 1,
65             handles => {
66             all_ersatz_methods => 'values',
67             _add_ersatz_method => 'set',
68             },
69             );
70 13     13   440 sub _build_ersatz_methods { +{} };
71             sub add_ersatz_method
72             {
73 16     16 0 51 my ($meta, $method) = @_;
74 16         464 $meta->_add_ersatz_method($method->name => $method);
75             }
76             sub apply_all_ersatz_methods_to_class
77             {
78 15     15 0 36 my ($self, $class) = @_;
79 15         538 for ($self->all_ersatz_methods)
80             {
81 15 100       391 next if $self->has_method($_->name);
82 13         505 $_->apply_to_class($class);
83             }
84             }
85             sub composition_class_roles
86             {
87 10     10 0 25730 return 'MooseX::ErsatzMethod::Trait::Composite';
88             }
89 5   50     956 $INC{ Module::Runtime::module_notional_filename(__PACKAGE__) } ||= __FILE__;
90             };
91              
92             BEGIN {
93             package MooseX::ErsatzMethod::Trait::Composite;
94 5     5   26358 our $AUTHORITY = 'cpan:TOBYINK';
95 5         13 our $VERSION = '0.005';
96 5     5   505 use Moose::Role;
  5         13  
  5         22  
97 5         21 with qw(MooseX::ErsatzMethod::Trait::Role);
98             around apply_params => sub
99             {
100 7         17639 my $orig = shift;
101 7         19 my $self = shift;
102 7         37 $self->$orig(@_);
103            
104 7         16487 $self = Moose::Util::MetaRole::apply_metaroles(
105             for => $self,
106             role_metaroles => \%METAROLES,
107             );
108 7         23091 $self->_merge_ersatz_methods;
109 7         170 return $self;
110 5         16618 };
111             sub _merge_ersatz_methods
112             {
113 7     7   30 my $self = shift;
114 7         19 foreach my $role (@{ $self->get_roles })
  7         273  
115             {
116 14 100       245 next unless Moose::Util::does_role(
117             $role,
118             'MooseX::ErsatzMethod::Trait::Role',
119             );
120 10         2559 $self->add_ersatz_method($_) for $role->all_ersatz_methods;
121             }
122             }
123 5   50     887 $INC{ Module::Runtime::module_notional_filename(__PACKAGE__) } ||= __FILE__;
124             };
125              
126             BEGIN {
127             package MooseX::ErsatzMethod::Trait::ApplicationToClass;
128 5     5   25865 our $AUTHORITY = 'cpan:TOBYINK';
129 5         13 our $VERSION = '0.005';
130 5     5   483 use Moose::Role;
  5         13  
  5         31  
131             before apply => sub
132             {
133 15         66683 my ($meta, $role, $class) = @_;
134 15 50       63 return unless Moose::Util::does_role(
135             $role,
136             'MooseX::ErsatzMethod::Trait::Role',
137             );
138 15         3659 $role->apply_all_ersatz_methods_to_class($class);
139 5         33 };
140 5   50     940 $INC{ Module::Runtime::module_notional_filename(__PACKAGE__) } ||= __FILE__;
141             };
142              
143             BEGIN {
144             package MooseX::ErsatzMethod::Trait::ApplicationToRole;
145 5     5   25342 our $AUTHORITY = 'cpan:TOBYINK';
146 5         15 our $VERSION = '0.005';
147 5     5   450 use Moose::Role;
  5         12  
  5         23  
148             before apply => sub
149             {
150 1         655 my ($meta, $role1, $role2) = @_;
151 1         4 $role2 = Moose::Util::MetaRole::apply_metaroles(
152             for => $role2,
153             role_metaroles => \%METAROLES,
154             );
155 1         3609 $role2->add_ersatz_method($_) for $role1->all_ersatz_methods;
156 5         34 };
157 5   50     875 $INC{ Module::Runtime::module_notional_filename(__PACKAGE__) } ||= __FILE__;
158             };
159              
160             BEGIN {
161             package MooseX::ErsatzMethod::Trait::ApplicationToInstance;
162 5     5   24929 our $AUTHORITY = 'cpan:TOBYINK';
163 5         12 our $VERSION = '0.005';
164 5     5   446 use Moose::Role;
  5         11  
  5         26  
165 5   50     19 $INC{ Module::Runtime::module_notional_filename(__PACKAGE__) } ||= __FILE__;
166             };
167              
168             Moose::Exporter->setup_import_methods(
169             with_meta => [ 'ersatz' ],
170             role_metaroles => \%METAROLES,
171             );
172              
173             sub ersatz
174             {
175 5     5 1 79035 my ($meta, $name, $coderef) = @_;
176            
177 5 50       34 Carp::confess('Ersatz methods can only be created for Moose roles; not classes. Stopped')
178             unless $meta->isa('Moose::Meta::Role');
179            
180 5         85 my $method;
181 5 50       27 if (Scalar::Util::blessed($name))
182             {
183 0         0 $method = $name;
184             }
185             else
186             {
187 5         57 $method = 'MooseX::ErsatzMethod::Meta::Method'->new(
188             code => $coderef,
189             name => $name,
190             associated_role => $meta,
191             );
192             }
193            
194 5         5660 $meta->add_ersatz_method($method);
195             }
196              
197             1;
198              
199             __END__
200              
201             =head1 NAME
202              
203             MooseX::ErsatzMethod - provide a method implementation that isn't as good as the real thing
204              
205             =head1 SYNOPSIS
206              
207             package Greetable;
208             use Moose::Role;
209             use MooseX::ErsatzMethod;
210            
211             sub greet {
212             my $self = shift;
213             say "Hello ", $self->name;
214             }
215            
216             ersatz name => sub {
217             my $self = shift;
218             return Scalar::Util::refaddr($self);
219             };
220              
221             package Person;
222             use Moose;
223             with 'Greetable';
224             has name => (is => 'ro', isa => 'Str');
225            
226             package Termite;
227             use Moose;
228             with 'Greetable';
229             # no need to implement 'name'.
230              
231             =head1 DESCRIPTION
232              
233             MooseX::ErsatzMethod provides a mechanism for Moose roles to provide fallback
234             implementations of methods that they really want for consuming classes to
235             implement. In the SYNOPSIS section, the C<Greetable> role really wants
236             consuming classes to implement a C<name> method. The C<Termite> class doesn't
237             implement C<name>, but it's OK, because C<Greetable> provides a fallback
238             (albeit rubbish) implementation of the method.
239              
240             B<< But wait! >> I hear you say. Don't roles already work that way? Can't a
241             role provide an implementation of a method which consuming classes can
242             override? Yes, they can. However, the precedence is:
243              
244             consuming class's implementation (wins)
245             role's implementation
246             inherited implementation (e.g. from parent class)
247              
248             That is, the role's method implementation overrides methods inherited from the
249             parent class. An ersatz method implementation sits right at the bottom of the
250             heirarchy; it is only used if the consuming class and its ancestors cannot
251             provide the method. (It still beats C<AUTOLOAD> though.)
252              
253             One other feature of ersatz methods is that they can never introduce role
254             composition conflicts. If you compose two different roles which both provide
255             ersatz method implementations, an arbitrary method implementation is selected.
256              
257             =head2 Functions
258              
259             =over
260              
261             =item C<< ersatz $name => $coderef >>
262              
263             Defines an ersatz function.
264              
265             =back
266              
267             =head2 Metarole Trait
268              
269             Your metarole (i.e. C<< $metarole = Greetable->meta >>) will have the
270             following additional methods:
271              
272             =over
273              
274             =item C<< ersatz_methods >>
275              
276             Returns a name => object hashref of ersatz methods for this class. The
277             objects are instances of L<< MooseX::ErsatzMethod::Meta::Method >>.
278              
279             =item C<< all_ersatz_methods >>
280              
281             Returns just the values (objects) from the C<ersatz_methods> hash.
282              
283             =item C<< add_ersatz_method($name, $coderef) >>
284              
285             Given a name and coderef, creates a L<< MooseX::ErsatzMethod::Meta::Method >>
286             object and adds it to the C<ersatz_methods> hash.
287              
288             =item C<< apply_all_ersatz_methods_to_class($class) >>
289              
290             Given a Moose::Meta::Class object, iterates through C<all_ersatz_methods>
291             applying each to the class. This procedure skips any ersatz method for which
292             this role can provide a real method.
293              
294             =back
295              
296             =head2 MooseX::ErsatzMethod::Meta::Method
297              
298             Instances of this class represent an ersatz method.
299              
300             =over
301              
302             =item C<< new(%attrs) >>
303              
304             Standard Moose constructor.
305              
306             =item C<< code >>
307              
308             The coderef for the method.
309              
310             =item C<< name >>
311              
312             The sub name for the method (not including the package).
313              
314             =item C<< associated_role >>
315              
316             The metarole associated with this method (if any).
317              
318             =item C<< apply_to_class($class) >>
319              
320             Given a Moose::Meta::Class object, installs this method into the class
321             unless the class (or a superclass) already has a method of that name.
322              
323             =back
324              
325             =head1 CAVEATS
326              
327             If you use one-at-a-time role composition, then ersatz methods in one
328             role might end up "beating" a proper method provided by another role.
329              
330             with 'Role1'; with 'Role2'; # No!
331             with qw( Role1 Role2 ); # Yes
332              
333             =head1 BUGS
334              
335             Please report any bugs to
336             L<http://rt.cpan.org/Dist/Display.html?Queue=MooseX-ErsatzMethod>.
337              
338             =head1 SEE ALSO
339              
340             L<Moose::Role>.
341              
342             L<https://speakerdeck.com/u/sartak/p/moose-role-usage-patterns?slide=32>.
343              
344             =head1 AUTHOR
345              
346             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
347              
348             =head1 COPYRIGHT AND LICENCE
349              
350             This software is copyright (c) 2012, 2014 by Toby Inkster.
351              
352             This is free software; you can redistribute it and/or modify it under
353             the same terms as the Perl 5 programming language system itself.
354              
355             =head1 DISCLAIMER OF WARRANTIES
356              
357             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
358             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
359             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
360