File Coverage

blib/lib/MooseX/AttributeShortcuts.pm
Criterion Covered Total %
statement 46 46 100.0
branch 9 10 90.0
condition 5 7 71.4
subroutine 12 12 100.0
pod 0 1 0.0
total 72 76 94.7


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;
11             our $AUTHORITY = 'cpan:RSRCHBOY';
12             # git description: 0.036-2-g03ef7bc
13             $MooseX::AttributeShortcuts::VERSION = '0.037';
14              
15             # ABSTRACT: Shorthand for common attribute options
16              
17 28     28   12549648 use strict;
  28         281  
  28         764  
18 28     28   145 use warnings;
  28         58  
  28         673  
19              
20 28     28   1401 use namespace::autoclean;
  28         33031  
  28         147  
21              
22 28     28   2007 use Moose 1.14 ();
  28         365467  
  28         533  
23 28     28   152 use Moose::Exporter;
  28         60  
  28         234  
24 28     28   1047 use Moose::Meta::TypeConstraint;
  28         55  
  28         593  
25 28     28   137 use Moose::Util::MetaRole;
  28         57  
  28         566  
26 28     28   126 use Moose::Util::TypeConstraints;
  28         50  
  28         199  
27              
28 28     28   60835 use MooseX::AttributeShortcuts::Trait::Attribute;
  28         96  
  28         1244  
29 28     28   11337 use MooseX::AttributeShortcuts::Trait::Role::Attribute;
  28         113  
  28         9511  
30              
31             my ($import, $unimport, $init_meta) = Moose::Exporter->build_import_methods(
32             install => [ 'unimport' ],
33             trait_aliases => [
34             [ 'MooseX::AttributeShortcuts::Trait::Attribute' => 'Shortcuts' ],
35             ],
36             );
37              
38             my $role_params;
39              
40             sub import {
41 32     32   36856 my ($class, %args) = @_;
42              
43 32         138 $role_params = {};
44 96 100       340 do { $role_params->{$_} = delete $args{"-$_"} if exists $args{"-$_"} }
45 32         105 for qw{ writer_prefix builder_prefix prefixes };
46              
47 32         152 @_ = ($class, %args);
48 32         165 goto &$import;
49             }
50              
51             sub init_meta {
52 33     33 0 3485 my ($class_name, %args) = @_;
53 33   100     309 my $params = delete $args{role_params} || $role_params || undef;
54 33         84 undef $role_params;
55              
56             # Just in case we do ever start to get an $init_meta from ME
57 33 50       102 $init_meta->($class_name, %args)
58             if $init_meta;
59              
60             # make sure we have a metaclass instance kicking around
61 33         72 my $for_class = $args{for_class};
62 33 100       125 die "Class $for_class has no metaclass!"
63             unless Class::MOP::class_of($for_class);
64              
65             # If we're given parameters to pass on to construct a role with, we build
66             # it out here rather than pass them on and allowing apply_metaroles() to
67             # handle it, as there are Very Loud Warnings about how parameterized roles
68             # are non-cacheable when generated on the fly.
69              
70             ### $params
71 32 100 50     496 my $trait
72             = ($params && scalar keys %$params)
73             ? MooseX::AttributeShortcuts::Trait::Attribute
74             ->meta
75             ->generate_role(parameters => $params)
76             : 'MooseX::AttributeShortcuts::Trait::Attribute'
77             ;
78              
79             my $role_attribute_trait
80             = ($params && exists $params->{builder_prefix})
81             ? MooseX::AttributeShortcuts::Trait::Role::Attribute
82             ->meta
83             ->generate_role(
84             parameters => { builder_prefix => $params->{builder_prefix} },
85             )
86 32 100 66     527 : 'MooseX::AttributeShortcuts::Trait::Role::Attribute'
87             ;
88              
89 32         546 Moose::Util::MetaRole::apply_metaroles(
90             # TODO add attribute trait here to create builder method if found
91             for => $for_class,
92             class_metaroles => { attribute => [ $trait ] },
93             role_metaroles => {
94             applied_attribute => [ $trait ],
95             # attribute => [ 'MooseX::AttributeShortcuts::Trait::Role::Attribute' ],
96             attribute => [ $role_attribute_trait ],
97             },
98             parameter_metaroles => { applied_attribute => [ $trait ] },
99             parameterized_role_metaroles => { applied_attribute => [ $trait ] },
100             );
101              
102 32         215896 return Class::MOP::class_of($for_class);
103             }
104              
105             1;
106              
107             __END__
108              
109             =pod
110              
111             =encoding UTF-8
112              
113             =for :stopwords Chris Weyl Alders David Etheridge Graham Karen Knop Olaf Steinbrunner
114             GitHub attribute's isa one's rwp SUBTYPING foo
115              
116             =head1 NAME
117              
118             MooseX::AttributeShortcuts - Shorthand for common attribute options
119              
120             =head1 VERSION
121              
122             This document describes version 0.037 of MooseX::AttributeShortcuts - released November 20, 2017 as part of MooseX-AttributeShortcuts.
123              
124             =head1 SYNOPSIS
125              
126             package Some::Class;
127              
128             use Moose;
129             use MooseX::AttributeShortcuts;
130              
131             # same as:
132             # is => 'ro', lazy => 1, builder => '_build_foo'
133             has foo => (is => 'lazy');
134              
135             # same as: is => 'ro', writer => '_set_foo'
136             has foo => (is => 'rwp');
137              
138             # same as: is => 'ro', builder => '_build_bar'
139             has bar => (is => 'ro', builder => 1);
140              
141             # same as: is => 'ro', clearer => 'clear_bar'
142             has bar => (is => 'ro', clearer => 1);
143              
144             # same as: is => 'ro', predicate => 'has_bar'
145             has bar => (is => 'ro', predicate => 1);
146              
147             # works as you'd expect for "private": predicate => '_has_bar'
148             has _bar => (is => 'ro', predicate => 1);
149              
150             # extending? Use the "Shortcuts" trait alias
151             extends 'Some::OtherClass';
152             has '+bar' => (traits => [Shortcuts], builder => 1, ...);
153              
154             =head1 DESCRIPTION
155              
156             Ever find yourself repeatedly specifying writers and builders, because there's
157             no good shortcut to specifying them? Sometimes you want an attribute to have
158             a read-only public interface, but a private writer. And wouldn't it be easier
159             to just say C<< builder => 1 >> and have the attribute construct the canonical
160             C<_build_$name> builder name for you?
161              
162             This package causes an attribute trait to be applied to all attributes defined
163             to the using class. This trait extends the attribute option processing to
164             handle the above variations. All attribute options as described in L<Moose>
165             or L<Class::MOP::Attribute> remain usable, just as when this trait is not
166             applied.
167              
168             =head2 Some Notes On History
169              
170             Moose has long had a L<lazy_build attribute option|Moose/lazy_build>. It was
171             once considered a best practice, but that has, ah, changed. This trait began
172             as a desire to still leverage bits of C<lazy_build> (and a tacit
173             acknowledgment that fat-finger bugs rank among the most embarrassing, right up
174             there with "the TV was unplugged the entire time").
175              
176             This author does not recommend you use C<lazy_build>, unless you know exactly
177             what you're doing (probably) and that it's a good idea (probably not).
178              
179             Nonetheless, this C<lazy_build> option is why we set certain options the way
180             we do below; while C<lazy_build> in its entirety is not optimal, it had the
181             right idea: regular, predictable accessor names for regular, predictable
182             attribute options.
183              
184             As an example, just looking at the below it doesn't seem logical that:
185              
186             has _foo => (is => 'ro', clearer => 1);
187              
188             ...becomes:
189              
190             has _foo => (is => 'ro', clearer => '_clear_foo');
191              
192             After reading the L<lazy_build attribute option|Moose/lazy_build>,
193             however, we see that the choice had already been made for us.
194              
195             =for Pod::Coverage init_meta
196              
197             =head1 USAGE
198              
199             This package automatically applies an attribute metaclass trait. Simply using
200             this package causes the trait to be applied by default to your attribute's
201             metaclasses.
202              
203             =head1 EXTENDING A CLASS
204              
205             If you're extending a class and trying to extend its attributes as well,
206             you'll find out that the trait is only applied to attributes defined locally
207             in the class. This package exports a trait shortcut function C<Shortcuts>
208             that will help you apply this to the extended attribute:
209              
210             has '+something' => (traits => [Shortcuts], ...);
211              
212             =head1 NEW ATTRIBUTE OPTIONS
213              
214             Unless specified here, all options defined by L<Moose::Meta::Attribute> and
215             L<Class::MOP::Attribute> remain unchanged.
216              
217             Want to see additional options? Ask, or better yet, fork on GitHub and send
218             a pull request. If the shortcuts you're asking for already exist in L<Moo> or
219             L<Mouse> or elsewhere, please note that as it will carry significant weight.
220              
221             For the following, C<$name> should be read as the attribute name; and the
222             various prefixes should be read using the defaults.
223              
224             =head2 is => 'rwp'
225              
226             Specifying C<is =E<gt> 'rwp'> will cause the following options to be set:
227              
228             is => 'ro'
229             writer => "_set_$name"
230              
231             rwp can be read as "read + write private".
232              
233             =head2 is => 'lazy'
234              
235             Specifying C<is =E<gt> 'lazy'> will cause the following options to be set:
236              
237             is => 'ro'
238             builder => "_build_$name"
239             lazy => 1
240              
241             B<NOTE:> Since 0.009 we no longer set C<init_arg =E<gt> undef> if no C<init_arg>
242             is explicitly provided. This is a change made in parallel with L<Moo>, based
243             on a large number of people surprised that lazy also made one's C<init_def>
244             undefined.
245              
246             =head2 is => 'lazy', default => ...
247              
248             Specifying C<is =E<gt> 'lazy'> and a default will cause the following options to be
249             set:
250              
251             is => 'ro'
252             lazy => 1
253             default => ... # as provided
254              
255             That is, if you specify C<is =E<gt> 'lazy'> and also provide a C<default>, then
256             we won't try to set a builder, as well.
257              
258             =head2 builder => 1
259              
260             Specifying C<builder =E<gt> 1> will cause the following options to be set:
261              
262             builder => "_build_$name"
263              
264             =head2 builder => sub { ... }
265              
266             Passing a coderef to builder will cause that coderef to be installed in the
267             class this attribute is associated with the name you'd expect, and
268             C<builder =E<gt> 1> to be set.
269              
270             e.g., in your class (or role),
271              
272             has foo => (is => 'ro', builder => sub { 'bar!' });
273              
274             ...is effectively the same as...
275              
276             has foo => (is => 'ro', builder => '_build_foo');
277             sub _build_foo { 'bar!' }
278              
279             The behaviour of this option in roles changed in 0.030, and the builder
280             methods will be installed in the role itself. This means you can
281             alias/exclude/etc builder methods in roles, just as you can with any other
282             method.
283              
284             =head2 clearer => 1
285              
286             Specifying C<clearer =E<gt> 1> will cause the following options to be set:
287              
288             clearer => "clear_$name"
289              
290             or, if your attribute name begins with an underscore:
291              
292             clearer => "_clear$name"
293              
294             (that is, an attribute named C<_foo> would get C<_clear_foo>)
295              
296             =head2 predicate => 1
297              
298             Specifying C<predicate =E<gt> 1> will cause the following options to be set:
299              
300             predicate => "has_$name"
301              
302             or, if your attribute name begins with an underscore:
303              
304             predicate => "_has$name"
305              
306             (that is, an attribute named C<_foo> would get C<_has_foo>)
307              
308             =head2 init_arg => 1 / -1
309              
310             This is a somewhat esoteric shortcut; you probably don't want to use this (or
311             even read this section).
312              
313             Specifying C<init_arg =E<gt> 1> will cause the following options to be set:
314              
315             # attribute: "name"
316             init_arg => 'name'
317              
318             # or, attribute: "_name"
319             init_arg => '_name'
320              
321             ...while C<init_arg =E<gt> -1> will cause the following options to be set:
322              
323             # attribute: "name"
324             init_arg => '_name'
325              
326             # or, attribute: "_name"
327             init_arg => 'name'
328              
329             =head2 trigger => 1
330              
331             Specifying C<trigger =E<gt> 1> will cause the attribute to be created with a trigger
332             that calls a named method in the class with the options passed to the trigger.
333             By default, the method name the trigger calls is the name of the attribute
334             prefixed with C<_trigger_>.
335              
336             e.g., for an attribute named C<foo> this would be equivalent to:
337              
338             trigger => sub { shift->_trigger_foo(@_) }
339              
340             For an attribute named C<_foo>:
341              
342             trigger => sub { shift->_trigger__foo(@_) }
343              
344             This naming scheme, in which the trigger is always private, is the same as the
345             builder naming scheme (just with a different prefix).
346              
347             =head2 handles => { foo => sub { ... }, ... }
348              
349             Creating a delegation with a coderef will now create a new, "custom accessor"
350             for the attribute. These coderefs will be installed and called as methods on
351             the associated class (just as readers, writers, and other accessors are), and
352             will have the attribute metaclass available in C<$_>. Anything the accessor
353             is called with it will have access to in C<@_>, just as you'd expect of a
354             method.
355              
356             e.g., the following example creates an attribute named C<bar> with a standard
357             reader accessor named C<bar> and two custom accessors named C<foo> and
358             C<foo_too>.
359              
360             has bar => (
361              
362             is => 'ro',
363             isa => 'Int',
364             handles => {
365              
366             foo => sub {
367             my $self = shift @_;
368              
369             return $_->get_value($self) + 1;
370             },
371              
372             foo_too => sub {
373             my $self = shift @_;
374              
375             return $self->bar + 1;
376             },
377              
378             # ...as you'd expect.
379             bar => 'bar',
380             },
381             );
382              
383             ...and later,
384              
385             Note that in this example both foo() and foo_too() do effectively the same
386             thing: return the attribute's current value plus 1. However, foo() accesses
387             the attribute value directly through the metaclass, the pros and cons of
388             which this author leaves as an exercise for the reader to determine.
389              
390             You may choose to use the installed accessors to get at the attribute's value,
391             or use the direct metaclass access, your choice.
392              
393             =head1 ANONYMOUS SUBTYPING AND COERCION
394              
395             "Abusus non tollit usum."
396              
397             Note that we create new, anonymous subtypes whenever the constraint or
398             coercion options are specified in such a way that the Shortcuts trait (this
399             one) is invoked. It's fully supported to use both constraint and coerce
400             options at the same time.
401              
402             This facility is intended to assist with the creation of one-off type
403             constraints and coercions. It is not possible to deliberately reuse the
404             subtypes we create, and if you find yourself using a particular isa /
405             constraint / coerce option triplet in more than one place you should really
406             think about creating a type that you can reuse. L<MooseX::Types> provides
407             the facilities to easily do this, or even a simple L<constant> definition at
408             the package level with an anonymous type stashed away for local use.
409              
410             =head2 isa => sub { ... }
411              
412             has foo => (
413             is => 'rw',
414             # $_ == $_[0] == the value to be validated
415             isa => sub { die unless $_[0] == 1 },
416             );
417              
418             # passes constraint
419             $thing->foo(1);
420              
421             # fails constraint
422             $thing->foo(5);
423              
424             Given a coderef, create a type constraint for the attribute. This constraint
425             will fail if the coderef dies, and pass otherwise.
426              
427             Astute users will note that this is the same way L<Moo> constraints work; we
428             use L<MooseX::Meta::TypeConstraint::Mooish> to implement the constraint.
429              
430             =head2 isa_instance_of => ...
431              
432             Given a package name, this option will create an C<isa> type constraint that
433             requires the value of the attribute be an instance of the class (or a
434             descendant class) given. That is,
435              
436             has foo => (is => 'ro', isa_instance_of => 'SomeThing');
437              
438             ...is effectively the same as:
439              
440             use Moose::TypeConstraints 'class_type';
441             has foo => (
442             is => 'ro',
443             isa => class_type('SomeThing'),
444             );
445              
446             ...but a touch less awkward.
447              
448             =head2 isa => ..., constraint => sub { ... }
449              
450             Specifying the constraint option with a coderef will cause a new subtype
451             constraint to be created, with the parent type being the type specified in the
452             C<isa> option and the constraint being the coderef supplied here.
453              
454             For example, only integers greater than 10 will pass this attribute's type
455             constraint:
456              
457             # value must be an integer greater than 10 to pass the constraint
458             has thinger => (
459             isa => 'Int',
460             constraint => sub { $_ > 10 },
461             # ...
462             );
463              
464             Note that if you supply a constraint, you must also provide an C<isa>.
465              
466             =head2 isa => ..., constraint => sub { ... }, coerce => 1
467              
468             Supplying a constraint and asking for coercion will "Just Work", that is, any
469             coercions that the C<isa> type has will still work.
470              
471             For example, let's say that you're using the C<File> type constraint from
472             L<MooseX::Types::Path::Class>, and you want an additional constraint that the
473             file must exist:
474              
475             has thinger => (
476             is => 'ro',
477             isa => File,
478             constraint => sub { !! $_->stat },
479             coerce => 1,
480             );
481              
482             C<thinger> will correctly coerce the string "/etc/passwd" to a
483             C<Path::Class:File>, and will only accept the coerced result as a value if
484             the file exists.
485              
486             =head2 coerce => [ Type => sub { ...coerce... }, ... ]
487              
488             Specifying the coerce option with a hashref will cause a new subtype to be
489             created and used (just as with the constraint option, above), with the
490             specified coercions added to the list. In the passed hashref, the keys are
491             Moose types (well, strings resolvable to Moose types), and the values are
492             coderefs that will coerce a given type to our type.
493              
494             has bar => (
495             is => 'ro',
496             isa => 'Str',
497             coerce => [
498             Int => sub { "$_" },
499             Object => sub { 'An instance of ' . ref $_ },
500             ],
501             );
502              
503             =head1 INTERACTIONS WITH OTHER ATTRIBUTE TRAITS
504              
505             Sometimes attribute traits interact in surprising ways. This trait is well
506             behaved; if you have discovered any interactions with other traits (good, bad,
507             indifferent, etc), please
508             L<report this|https://github.com/RsrchBoy/moosex-attributeshortcuts/issues/new>
509             so that it can be worked around, fixed, or documented, as appropriate.
510              
511             =head2 MooseX::SemiAffordanceAccessor
512              
513             L<MooseX::SemiAffordanceAccessor> changes how the C<< is => 'rw' >> and
514             C<< accessor => ... >> attribute options work. If our trait detects that an
515             attribute has had the
516             L<MooseX::SemiAffordanceAccessor attribute trait|MooseX::SemiAffordanceAccessor::Role::Attribute>
517             applied, then we change our behaviour to conform to its expectations:
518              
519             =over 4
520              
521             =item *
522              
523             C<< is => 'rwp' >>
524              
525             This:
526              
527             has foo => (is => 'rwp');
528             has _bar => (is => 'rwp');
529              
530             ...is now effectively equivalent to:
531              
532             has foo => (is => 'ro', writer => '_set_foo');
533             has _bar => (is => 'ro', writer => '_set_bar')
534              
535             =item *
536              
537             C<-writer_prefix> is ignored
538              
539             ...as MooseX::SemiAffordanceAccessor has its own specific ideas as to how
540             writers should look.
541              
542             =back
543              
544             =head1 SEE ALSO
545              
546             Please see those modules/websites for more information related to this module.
547              
548             =over 4
549              
550             =item *
551              
552             L<Moo|Moo>
553              
554             =item *
555              
556             L<MooseX::Types|MooseX::Types>
557              
558             =item *
559              
560             L<MooseX::SemiAffordanceAccessor|MooseX::SemiAffordanceAccessor>
561              
562             =back
563              
564             =head1 BUGS
565              
566             Please report any bugs or feature requests on the bugtracker website
567             L<https://github.com/RsrchBoy/moosex-attributeshortcuts/issues>
568              
569             When submitting a bug or request, please include a test-file or a
570             patch to an existing test-file that illustrates the bug or desired
571             feature.
572              
573             =head1 AUTHOR
574              
575             Chris Weyl <cweyl@alumni.drew.edu>
576              
577             =head1 CONTRIBUTORS
578              
579             =for stopwords David Steinbrunner Graham Knop Karen Etheridge Olaf Alders
580              
581             =over 4
582              
583             =item *
584              
585             David Steinbrunner <dsteinbrunner@pobox.com>
586              
587             =item *
588              
589             Graham Knop <haarg@haarg.org>
590              
591             =item *
592              
593             Karen Etheridge <ether@cpan.org>
594              
595             =item *
596              
597             Olaf Alders <olaf@wundersolutions.com>
598              
599             =back
600              
601             =head1 COPYRIGHT AND LICENSE
602              
603             This software is Copyright (c) 2017, 2015, 2014, 2013, 2012, 2011 by Chris Weyl.
604              
605             This is free software, licensed under:
606              
607             The GNU Lesser General Public License, Version 2.1, February 1999
608              
609             =cut