File Coverage

blib/lib/MooseX/AttributeShortcuts/Trait/Attribute.pm
Criterion Covered Total %
statement 127 135 94.0
branch 46 62 74.1
condition 9 12 75.0
subroutine 30 31 96.7
pod 1 1 100.0
total 213 241 88.3


line stmt bran cond sub pod time code
1             #
2             # This file is part of MooseX-AttributeShortcuts
3             #
4             # This software is Copyright (c) 2017, 2015, 2014, 2013, 2012, 2011 by Chris Weyl.
5             #
6             # This is free software, licensed under:
7             #
8             # The GNU Lesser General Public License, Version 2.1, February 1999
9             #
10             package MooseX::AttributeShortcuts::Trait::Attribute;
11             our $AUTHORITY = 'cpan:RSRCHBOY';
12             $MooseX::AttributeShortcuts::Trait::Attribute::VERSION = '0.036';
13             # ABSTRACT: Shortcuts attribute trait proper
14              
15 27     27   184 use namespace::autoclean;
  27         64  
  27         177  
16 27     27   9667 use MooseX::Role::Parameterized;
  27         2146168  
  27         128  
17 27     27   988541 use Moose::Util::TypeConstraints ':all';
  27         81  
  27         286  
18 27     27   65090 use MooseX::Types::Moose ':all';
  27         1349759  
  27         317  
19 27     27   236342 use MooseX::Types::Common::String ':all';
  27         2586947  
  27         279  
20 27     27   140185 use MooseX::Util;
  27         1262964  
  27         262  
21              
22 27     27   15029 use aliased 'MooseX::Meta::TypeConstraint::Mooish' => 'MooishTC';
  27         16521  
  27         241  
23 27     27   823233 use aliased 'MooseX::AttributeShortcuts::Trait::Method::Builder' => 'BuilderTrait';
  27         105  
  27         168  
24              
25 27     27   2955 use List::Util 1.33 'any';
  27         726  
  27         47219  
26              
27             # lazy...
28             my $_acquire_isa_tc = sub { goto \&Moose::Util::TypeConstraints::find_or_create_isa_type_constraint };
29              
30              
31             parameter writer_prefix => (isa => NonEmptySimpleStr, default => '_set_');
32             parameter builder_prefix => (isa => NonEmptySimpleStr, default => '_build_');
33              
34             with 'MooseX::AttributeShortcuts::Trait::Attribute::HasAnonBuilder';
35              
36              
37             has constraint => (
38             is => 'ro',
39             isa => 'CodeRef',
40             predicate => 'has_constraint',
41             );
42              
43             has original_isa => (
44             is => 'ro',
45             predicate => 'has_original_isa',
46             );
47              
48             has trigger_method => (
49             is => 'ro',
50             predicate => 'has_trigger_method',
51             );
52              
53              
54             after attach_to_class => sub {
55             my ($self, $class) = @_;
56              
57             return unless $self->has_anon_builder && !$self->anon_builder_installed;
58              
59             ### install our anon builder as a method: $class->name
60             # $class->add_method($self->builder => $self->anon_builder);
61             $class->add_method($self->builder => $self->_builder_method_meta($class));
62             $self->_set_anon_builder_installed;
63              
64             return;
65             };
66              
67              
68             before _process_options => sub { shift->_mxas_process_options(@_) };
69              
70             # this feels... bad. But I'm not sure there's any way to ensure we
71             # process options on a clone/extends without wrapping new().
72              
73             around new => sub {
74             my ($orig, $self) = (shift, shift);
75             my ($name, %options) = @_;
76              
77             $self->_mxas_process_options($name, \%options)
78             if $options{__hack_no_process_options};
79              
80             return $self->$orig($name, %options);
81             };
82              
83              
84             # NOTE: remove_delegation() will also automagically remove any custom
85             # accessors we create here
86              
87             # handle: handles => { name => sub { ... }, ... }
88             around _make_delegation_method => sub {
89             my ($orig, $self) = (shift, shift);
90             my ($name, $coderef) = @_;
91              
92             ### _make_delegation_method() called with a: ref $coderef
93             return $self->$orig(@_)
94             unless 'CODE' eq ref $coderef;
95              
96             # this coderef will be installed as a method on the associated class itself.
97             my $custom_coderef = sub {
98             # aka $self from the class instance's perspective
99 2         9048 my $associated_class_instance = shift @_;
100              
101             # in $coderef, $_ will be the attribute metaclass
102 2         6 local $_ = $self;
103 2         7 return $associated_class_instance->$coderef(@_);
104             };
105              
106             return $self->_process_accessors(custom => { $name => $custom_coderef });
107             };
108              
109             sub _mxas_process_options {
110 60     60   183 my ($class, $name, $options) = @_;
111              
112 60     673   323 my $_has = sub { defined $options->{$_[0]} };
  673         2866  
113 60 100   301   261 my $_opt = sub { $_has->(@_) ? $options->{$_[0]} : q{} };
  301         548  
114 60 100   150   244 my $_ref = sub { ref $_opt->(@_) || q{} };
  150         366  
115              
116             # handle: is => ...
117 60         308 $class->_mxas_is_rwp($name, $options, $_has, $_opt, $_ref);
118 60         293 $class->_mxas_is_lazy($name, $options, $_has, $_opt, $_ref);
119              
120             # handle: builder => 1, builder => sub { ... }
121 60         284 $class->_mxas_builder($name, $options, $_has, $_opt, $_ref);
122              
123             # handle: isa_instance_of => ...
124 60         285 $class->_mxas_isa_instance_of($name, $options, $_has, $_opt, $_ref);
125             # handle: isa => sub { ... }
126 60         278 $class->_mxas_isa_mooish($name, $options, $_has, $_opt, $_ref);
127              
128             # handle: constraint => ...
129 60         296 $class->_mxas_constraint($name, $options, $_has, $_opt, $_ref);
130             # handle: coerce => [ ... ]
131 60         301 $class->_mxas_coerce($name, $options, $_has, $_opt, $_ref);
132              
133              
134 60         300 my %prefix = (
135             predicate => 'has',
136             clearer => 'clear',
137             trigger => '_trigger_',
138             );
139              
140 60 100   32   278 my $is_private = sub { $name =~ /^_/ ? $_[0] : $_[1] };
  32         98  
141             my $default_for = sub {
142 120     120   283 my ($opt) = @_;
143              
144 120 100       240 return unless $_has->($opt);
145 16         30 my $opt_val = $_opt->($opt);
146              
147 16 50       57 my ($head, $mid)
    100          
148             = $opt_val eq '1' ? ($is_private->('_', q{}), $is_private->(q{}, '_'))
149             : $opt_val eq '-1' ? ($is_private->(q{}, '_'), $is_private->(q{}, '_'))
150             : return;
151              
152 16         51 $options->{$opt} = $head . $prefix{$opt} . $mid . $name;
153 16         35 return;
154 60         307 };
155              
156             ### set our other defaults, if requested...
157 60         235 $default_for->($_) for qw{ predicate clearer };
158 60         194 my $trigger = "$prefix{trigger}$name";
159 5     10   18 do { $options->{trigger} = sub { shift->$trigger(@_) }; $options->{trigger_method} = $trigger }
  10         38007  
  5         14  
160 60 100 66     244 if $options->{trigger} && $options->{trigger} eq '1';
161              
162 60         782 return;
163             }
164              
165             # The following two methods are here both to help ensure compatibility with
166             # MooseX::SemiAffordanceAccessor and to enable other packages to modify our
167             # behaviour.
168              
169             sub _mxas_writer_name {
170 14     14   46 my ($class, $name) = @_;
171              
172 14 50       81 return $class->canonical_writer_prefix . $name
173             unless $class->meta->does_role('MooseX::SemiAffordanceAccessor::Role::Attribute');
174              
175             # ok, if we're here then we need to follow that role's scheme
176 0 0       0 return $name =~ /^_/ ? "_set$name" : "set_$name";
177             };
178              
179             sub _mxas_private_writer_name {
180 14     14   48 my ($class, $name) = @_;
181              
182 14         67 $name = $class->_mxas_writer_name($name);
183 14 50       280 return $name =~ /^_/ ? $name : "_$name";
184             }
185              
186             # handle: is => 'rwp'
187             sub _mxas_is_rwp {
188 60     60   201 my ($class, $name, $options, $_has, $_opt, $_ref) = @_;
189              
190 60 100       161 return unless $_opt->('is') eq 'rwp';
191              
192 14         46 $options->{is} = 'ro';
193 14         68 $options->{writer} = $class->_mxas_private_writer_name($name);
194              
195 14         39 return;
196             }
197              
198             # handle: is => 'lazy'
199             sub _mxas_is_lazy {
200 60     60   190 my ($class, $name, $options, $_has, $_opt, $_ref) = @_;
201              
202 60 100       156 return unless $_opt->('is') eq 'lazy';
203              
204 5         15 $options->{is} = 'ro';
205 5         16 $options->{lazy} = 1;
206 5 100 100     15 $options->{builder} = 1
207             unless $_has->('builder') || $_has->('default');
208              
209 5         14 return;
210             }
211              
212             # handle: lazy_build => 'private'
213             sub _mxas_lazy_build_private {
214 0     0   0 my ($class, $name, $options, $_has, $_opt, $_ref) = @_;
215              
216 0 0       0 return unless $_opt->('lazy_build') eq 'private';
217              
218 0         0 $options->{lazy_build} = 1;
219 0         0 $options->{clearer} = "_clear_$name";
220 0         0 $options->{predicate} = "_has_$name";
221              
222 0         0 return;
223             }
224              
225             # handle: builder => 1, builder => sub { ... }
226             sub _mxas_builder {
227 60     60   182 my ($class, $name, $options, $_has, $_opt, $_ref) = @_;
228              
229 60 100       140 return unless $_has->('builder');
230              
231 24 100       84 if ($_ref->('builder') eq 'CODE') {
232              
233 3         9 $options->{anon_builder} = $options->{builder};
234 3         6 $options->{builder} = 1;
235             }
236              
237             $options->{builder} = $class->_mxas_builder_name($name)
238 24 100       177 if $options->{builder} eq '1';
239              
240 24         233 return;
241             }
242              
243             sub _mxas_isa_mooish {
244 60     60   187 my ($class, $name, $options, $_has, $_opt, $_ref) = @_;
245              
246 60 100       163 return unless $_ref->('isa') eq 'CODE';
247              
248             ### build a mooish type constraint...
249 1         3 $options->{original_isa} = $options->{isa};
250 1         19 $options->{isa} = MooishTC->new(constraint => $options->{isa});
251              
252 1         1982 return;
253             }
254              
255             # handle: isa_instance_of => ...
256             sub _mxas_isa_instance_of {
257 60     60   187 my ($class, $name, $options, $_has, $_opt, $_ref) = @_;
258              
259 60 100       146 return unless $_has->('isa_instance_of');
260              
261 1 50       4 if ($_has->('isa')) {
262              
263             $class->throw_error(
264             q{Cannot use 'isa_instance_of' and 'isa' together for attribute }
265 0         0 . $_opt->('definition_context')->{package} . '::' . $name
266             );
267             }
268              
269 1         8 $options->{isa} = class_type(delete $options->{isa_instance_of});
270              
271 1         2114 return;
272             }
273              
274             # handle: coerce => [ ... ]
275             sub _mxas_coerce {
276 60     60   185 my ($class, $name, $options, $_has, $_opt, $_ref) = @_;
277              
278 60 100       151 if ($_ref->('coerce') eq 'ARRAY') {
279              
280             ### must be type => sub { ... } pairs...
281 1         3 my @coercions = @{ $_opt->('coerce') };
  1         2  
282 1 50       3 confess 'You must specify an "isa" when declaring "coercion"'
283             unless $_has->('isa');
284 1 50       4 confess 'coercion array must be in pairs!'
285             if @coercions % 2;
286 1 50       3 confess 'must define at least one coercion pair!'
287             unless @coercions > 0;
288              
289 1         10 my $our_coercion = Moose::Meta::TypeCoercion->new;
290             my $our_type
291             = $options->{original_isa}
292             ? $options->{isa}
293 1 50       427 : $_acquire_isa_tc->($_opt->('isa'))->create_child_type
294             ;
295              
296 1         2356 $our_coercion->add_type_coercions(@coercions);
297 1         417 $our_type->coercion($our_coercion);
298              
299 1   33     18 $options->{original_isa} ||= $options->{isa};
300 1         6 $options->{isa} = $our_type;
301 1         3 $options->{coerce} = 1;
302              
303 1         4 return;
304             }
305              
306             # If our original constraint has coercions and our created subtype
307             # did not have any (as specified in the 'coerce' option), then
308             # copy the parent's coercions over.
309              
310 59 100 100     169 if ($_has->('original_isa') && $_opt->('coerce') eq '1') {
311              
312 1         4 my $isa_type = $_acquire_isa_tc->($_opt->('original_isa'));
313              
314 1 50       84 if ($isa_type->has_coercion) {
315              
316             # create our coercion as a copy of the parent
317             $_opt->('isa')->coercion(Moose::Meta::TypeCoercion->new(
318             type_constraint => $_opt->('isa'),
319 1         272 type_coercion_map => [ @{ $isa_type->coercion->type_coercion_map } ],
  1         7  
320             ));
321             }
322              
323             }
324              
325 59         2165 return;
326             }
327              
328             sub _mxas_constraint {
329 60     60   222 my ($class, $name, $options, $_has, $_opt, $_ref) = @_;
330              
331 60 100       168 return unless $_has->('constraint');
332              
333             # check for errors...
334 3 50       10 $class->throw_error('You must specify an "isa" when declaring a "constraint"')
335             if !$_has->('isa');
336 3 50       8 $class->throw_error('"constraint" must be a CODE reference')
337             if $_ref->('constraint') ne 'CODE';
338              
339             # constraint checking! XXX message, etc?
340 3 50       9 push my @opts, constraint => $_opt->('constraint')
341             if $_ref->('constraint') eq 'CODE';
342              
343             # stash our original option away and construct our new one
344 3         9 my $isa = $options->{original_isa} = $_opt->('isa');
345 3         12 $options->{isa} = $_acquire_isa_tc->($isa)->create_child_type(@opts);
346              
347 3         3694 return;
348             }
349              
350              
351             sub builder_method_metaclass {
352 3     3 1 9 my $self = shift @_;
353              
354 3         37 return with_traits($self->associated_class->method_metaclass => BuilderTrait);
355             }
356              
357              
358             role {
359             my $p = shift @_;
360              
361 14     14   7046 method canonical_writer_prefix => sub { $p->writer_prefix };
        4      
362 18     18   113 method canonical_builder_prefix => sub { $p->builder_prefix };
        6      
363             };
364              
365             !!42;
366              
367             __END__
368              
369             =pod
370              
371             =encoding UTF-8
372              
373             =for :stopwords Chris Weyl Alders David Etheridge Graham Karen Knop Olaf Steinbrunner
374              
375             =head1 NAME
376              
377             MooseX::AttributeShortcuts::Trait::Attribute - Shortcuts attribute trait proper
378              
379             =head1 VERSION
380              
381             This document describes version 0.036 of MooseX::AttributeShortcuts::Trait::Attribute - released October 31, 2017 as part of MooseX-AttributeShortcuts.
382              
383             =head1 DESCRIPTION
384              
385             This is the actual attribute trait that implements
386             L<MooseX::AttributeShortcuts>. You should consult that package's
387             documentation for information on any of the new attribute options; we're
388             mainly going to document the additional attributes, methods, and role
389             parameters that this role provides.
390              
391             All methods we include that chain off Moose's C<_process_options()> are
392             prefixed with C<_mxas_> and generally are not documented in the POD; we
393             document any internal methods of L<Moose::Meta::Attribute> that we wrap or
394             otherwise override we document here as well.
395              
396             =head1 ROLE PARAMETERS
397              
398             Parameterized roles accept parameters that influence their construction. This role accepts the following parameters.
399              
400             =head2 writer_prefix
401              
402             =head2 builder_prefix
403              
404             =head1 ATTRIBUTES
405              
406             =head2 constraint
407              
408             CodeRef, read-only.
409              
410             =head2 original_isa
411              
412             =head2 trigger_method
413              
414             Contains the name of the method that will be invoked as a trigger.
415              
416             =head1 BEFORE METHOD MODIFIERS
417              
418             =head2 _process_options
419              
420             Here we wrap _process_options() instead of the newer _process_is_option(), as
421             that makes our life easier from a Moose 1.x/2.x compatibility perspective --
422             and that we're generally altering more than just the 'is' option at one time.
423              
424             =head1 AROUND METHOD MODIFIERS
425              
426             =head2 _make_delegation_method
427              
428             Here we create and install any custom accessors that have been defined.
429              
430             =head1 AFTER METHOD MODIFIERS
431              
432             =head2 attach_to_class
433              
434             We hijack attach_to_class in order to install our anon_builder, if we have
435             one. Note that we don't go the normal associate_method/install_accessor/etc
436             route as this is kinda... different. (That is, the builder is not an
437             accessor of this attribute, and should not be installed as such.)
438              
439             =head1 METHODS
440              
441             =head2 has_constraint
442              
443             Predicate for the L</constraint> attribute.
444              
445             =head2 has_original_isa
446              
447             Predicate for the L</original_isa> attribute.
448              
449             =head2 has_trigger_method
450              
451             Predicate for the L</trigger_method> attribute.
452              
453             =head2 builder_method_metaclass()
454              
455             Returns the metaclass we'll use to install a inline builder.
456              
457             =head2 canonical_writer_prefix
458              
459             Returns the writer prefix; this is almost always C<set_>.
460              
461             =head2 canonical_builder_prefix
462              
463             Returns the builder prefix; this is almost always C<_build_>.
464              
465             =head1 PREFIXES
466              
467             We accept two parameters on the use of this module; they impact how builders
468             and writers are named.
469              
470             =head2 -writer_prefix
471              
472             use MooseX::::AttributeShortcuts -writer_prefix => 'prefix';
473              
474             The default writer prefix is C<_set_>. If you'd prefer it to be something
475             else (say, C<_>), this is where you'd do that.
476              
477             =head2 -builder_prefix
478              
479             use MooseX::AttributeShortcuts -builder_prefix => 'prefix';
480              
481             The default builder prefix is C<_build_>, as this is what L<Moose/lazy_build>
482             does, and what people in general recognize as build methods.
483              
484             =head1 SEE ALSO
485              
486             Please see those modules/websites for more information related to this module.
487              
488             =over 4
489              
490             =item *
491              
492             L<MooseX::AttributeShortcuts|MooseX::AttributeShortcuts>
493              
494             =back
495              
496             =head1 BUGS
497              
498             Please report any bugs or feature requests on the bugtracker website
499             L<https://github.com/RsrchBoy/moosex-attributeshortcuts/issues>
500              
501             When submitting a bug or request, please include a test-file or a
502             patch to an existing test-file that illustrates the bug or desired
503             feature.
504              
505             =head1 AUTHOR
506              
507             Chris Weyl <cweyl@alumni.drew.edu>
508              
509             =head1 COPYRIGHT AND LICENSE
510              
511             This software is Copyright (c) 2017, 2015, 2014, 2013, 2012, 2011 by Chris Weyl.
512              
513             This is free software, licensed under:
514              
515             The GNU Lesser General Public License, Version 2.1, February 1999
516              
517             =cut