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.035-1-gb1a3ab6
13             $MooseX::AttributeShortcuts::VERSION = '0.036';
14              
15             # ABSTRACT: Shorthand for common attribute options
16              
17 27     27   11958942 use strict;
  27         257  
  27         769  
18 27     27   148 use warnings;
  27         50  
  27         850  
19              
20 27     27   1343 use namespace::autoclean;
  27         37754  
  27         149  
21              
22 27     27   1998 use Moose 1.14 ();
  27         369347  
  27         551  
23 27     27   149 use Moose::Exporter;
  27         55  
  27         266  
24 27     27   1049 use Moose::Meta::TypeConstraint;
  27         58  
  27         615  
25 27     27   142 use Moose::Util::MetaRole;
  27         55  
  27         584  
26 27     27   134 use Moose::Util::TypeConstraints;
  27         57  
  27         218  
27              
28 27     27   62857 use MooseX::AttributeShortcuts::Trait::Attribute;
  27         112  
  27         1312  
29 27     27   11372 use MooseX::AttributeShortcuts::Trait::Role::Attribute;
  27         107  
  27         9534  
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 31     31   37307 my ($class, %args) = @_;
42              
43 31         85 $role_params = {};
44 93 100       317 do { $role_params->{$_} = delete $args{"-$_"} if exists $args{"-$_"} }
45 31         99 for qw{ writer_prefix builder_prefix prefixes };
46              
47 31         111 @_ = ($class, %args);
48 31         157 goto &$import;
49             }
50              
51             sub init_meta {
52 32     32 0 3384 my ($class_name, %args) = @_;
53 32   100     313 my $params = delete $args{role_params} || $role_params || undef;
54 32         89 undef $role_params;
55              
56             # Just in case we do ever start to get an $init_meta from ME
57 32 50       105 $init_meta->($class_name, %args)
58             if $init_meta;
59              
60             # make sure we have a metaclass instance kicking around
61 32         78 my $for_class = $args{for_class};
62 32 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 31 100 50     437 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 31 100 66     776 : 'MooseX::AttributeShortcuts::Trait::Role::Attribute'
87             ;
88              
89 31         510 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 31         206902 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.036 of MooseX::AttributeShortcuts - released October 31, 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 trigger => 1
309              
310             Specifying C<trigger =E<gt> 1> will cause the attribute to be created with a trigger
311             that calls a named method in the class with the options passed to the trigger.
312             By default, the method name the trigger calls is the name of the attribute
313             prefixed with C<_trigger_>.
314              
315             e.g., for an attribute named C<foo> this would be equivalent to:
316              
317             trigger => sub { shift->_trigger_foo(@_) }
318              
319             For an attribute named C<_foo>:
320              
321             trigger => sub { shift->_trigger__foo(@_) }
322              
323             This naming scheme, in which the trigger is always private, is the same as the
324             builder naming scheme (just with a different prefix).
325              
326             =head2 handles => { foo => sub { ... }, ... }
327              
328             Creating a delegation with a coderef will now create a new, "custom accessor"
329             for the attribute. These coderefs will be installed and called as methods on
330             the associated class (just as readers, writers, and other accessors are), and
331             will have the attribute metaclass available in C<$_>. Anything the accessor
332             is called with it will have access to in C<@_>, just as you'd expect of a
333             method.
334              
335             e.g., the following example creates an attribute named C<bar> with a standard
336             reader accessor named C<bar> and two custom accessors named C<foo> and
337             C<foo_too>.
338              
339             has bar => (
340              
341             is => 'ro',
342             isa => 'Int',
343             handles => {
344              
345             foo => sub {
346             my $self = shift @_;
347              
348             return $_->get_value($self) + 1;
349             },
350              
351             foo_too => sub {
352             my $self = shift @_;
353              
354             return $self->bar + 1;
355             },
356              
357             # ...as you'd expect.
358             bar => 'bar',
359             },
360             );
361              
362             ...and later,
363              
364             Note that in this example both foo() and foo_too() do effectively the same
365             thing: return the attribute's current value plus 1. However, foo() accesses
366             the attribute value directly through the metaclass, the pros and cons of
367             which this author leaves as an exercise for the reader to determine.
368              
369             You may choose to use the installed accessors to get at the attribute's value,
370             or use the direct metaclass access, your choice.
371              
372             =head1 ANONYMOUS SUBTYPING AND COERCION
373              
374             "Abusus non tollit usum."
375              
376             Note that we create new, anonymous subtypes whenever the constraint or
377             coercion options are specified in such a way that the Shortcuts trait (this
378             one) is invoked. It's fully supported to use both constraint and coerce
379             options at the same time.
380              
381             This facility is intended to assist with the creation of one-off type
382             constraints and coercions. It is not possible to deliberately reuse the
383             subtypes we create, and if you find yourself using a particular isa /
384             constraint / coerce option triplet in more than one place you should really
385             think about creating a type that you can reuse. L<MooseX::Types> provides
386             the facilities to easily do this, or even a simple L<constant> definition at
387             the package level with an anonymous type stashed away for local use.
388              
389             =head2 isa => sub { ... }
390              
391             has foo => (
392             is => 'rw',
393             # $_ == $_[0] == the value to be validated
394             isa => sub { die unless $_[0] == 1 },
395             );
396              
397             # passes constraint
398             $thing->foo(1);
399              
400             # fails constraint
401             $thing->foo(5);
402              
403             Given a coderef, create a type constraint for the attribute. This constraint
404             will fail if the coderef dies, and pass otherwise.
405              
406             Astute users will note that this is the same way L<Moo> constraints work; we
407             use L<MooseX::Meta::TypeConstraint::Mooish> to implement the constraint.
408              
409             =head2 isa_instance_of => ...
410              
411             Given a package name, this option will create an C<isa> type constraint that
412             requires the value of the attribute be an instance of the class (or a
413             descendant class) given. That is,
414              
415             has foo => (is => 'ro', isa_instance_of => 'SomeThing');
416              
417             ...is effectively the same as:
418              
419             use Moose::TypeConstraints 'class_type';
420             has foo => (
421             is => 'ro',
422             isa => class_type('SomeThing'),
423             );
424              
425             ...but a touch less awkward.
426              
427             =head2 isa => ..., constraint => sub { ... }
428              
429             Specifying the constraint option with a coderef will cause a new subtype
430             constraint to be created, with the parent type being the type specified in the
431             C<isa> option and the constraint being the coderef supplied here.
432              
433             For example, only integers greater than 10 will pass this attribute's type
434             constraint:
435              
436             # value must be an integer greater than 10 to pass the constraint
437             has thinger => (
438             isa => 'Int',
439             constraint => sub { $_ > 10 },
440             # ...
441             );
442              
443             Note that if you supply a constraint, you must also provide an C<isa>.
444              
445             =head2 isa => ..., constraint => sub { ... }, coerce => 1
446              
447             Supplying a constraint and asking for coercion will "Just Work", that is, any
448             coercions that the C<isa> type has will still work.
449              
450             For example, let's say that you're using the C<File> type constraint from
451             L<MooseX::Types::Path::Class>, and you want an additional constraint that the
452             file must exist:
453              
454             has thinger => (
455             is => 'ro',
456             isa => File,
457             constraint => sub { !! $_->stat },
458             coerce => 1,
459             );
460              
461             C<thinger> will correctly coerce the string "/etc/passwd" to a
462             C<Path::Class:File>, and will only accept the coerced result as a value if
463             the file exists.
464              
465             =head2 coerce => [ Type => sub { ...coerce... }, ... ]
466              
467             Specifying the coerce option with a hashref will cause a new subtype to be
468             created and used (just as with the constraint option, above), with the
469             specified coercions added to the list. In the passed hashref, the keys are
470             Moose types (well, strings resolvable to Moose types), and the values are
471             coderefs that will coerce a given type to our type.
472              
473             has bar => (
474             is => 'ro',
475             isa => 'Str',
476             coerce => [
477             Int => sub { "$_" },
478             Object => sub { 'An instance of ' . ref $_ },
479             ],
480             );
481              
482             =head1 INTERACTIONS WITH OTHER ATTRIBUTE TRAITS
483              
484             Sometimes attribute traits interact in surprising ways. This trait is well
485             behaved; if you have discovered any interactions with other traits (good, bad,
486             indifferent, etc), please
487             L<report this|https://github.com/RsrchBoy/moosex-attributeshortcuts/issues/new>
488             so that it can be worked around, fixed, or documented, as appropriate.
489              
490             =head2 MooseX::SemiAffordanceAccessor
491              
492             L<MooseX::SemiAffordanceAccessor> changes how the C<< is => 'rw' >> and
493             C<< accessor => ... >> attribute options work. If our trait detects that an
494             attribute has had the
495             L<MooseX::SemiAffordanceAccessor attribute trait|MooseX::SemiAffordanceAccessor::Role::Attribute>
496             applied, then we change our behaviour to conform to its expectations:
497              
498             =over 4
499              
500             =item *
501              
502             C<< is => 'rwp' >>
503              
504             This:
505              
506             has foo => (is => 'rwp');
507             has _bar => (is => 'rwp');
508              
509             ...is now effectively equivalent to:
510              
511             has foo => (is => 'ro', writer => '_set_foo');
512             has _bar => (is => 'ro', writer => '_set_bar')
513              
514             =item *
515              
516             C<-writer_prefix> is ignored
517              
518             ...as MooseX::SemiAffordanceAccessor has its own specific ideas as to how
519             writers should look.
520              
521             =back
522              
523             =head1 SEE ALSO
524              
525             Please see those modules/websites for more information related to this module.
526              
527             =over 4
528              
529             =item *
530              
531             L<Moo|Moo>
532              
533             =item *
534              
535             L<MooseX::Types|MooseX::Types>
536              
537             =item *
538              
539             L<MooseX::SemiAffordanceAccessor|MooseX::SemiAffordanceAccessor>
540              
541             =back
542              
543             =head1 BUGS
544              
545             Please report any bugs or feature requests on the bugtracker website
546             L<https://github.com/RsrchBoy/moosex-attributeshortcuts/issues>
547              
548             When submitting a bug or request, please include a test-file or a
549             patch to an existing test-file that illustrates the bug or desired
550             feature.
551              
552             =head1 AUTHOR
553              
554             Chris Weyl <cweyl@alumni.drew.edu>
555              
556             =head1 CONTRIBUTORS
557              
558             =for stopwords David Steinbrunner Graham Knop Karen Etheridge Olaf Alders
559              
560             =over 4
561              
562             =item *
563              
564             David Steinbrunner <dsteinbrunner@pobox.com>
565              
566             =item *
567              
568             Graham Knop <haarg@haarg.org>
569              
570             =item *
571              
572             Karen Etheridge <ether@cpan.org>
573              
574             =item *
575              
576             Olaf Alders <olaf@wundersolutions.com>
577              
578             =back
579              
580             =head1 COPYRIGHT AND LICENSE
581              
582             This software is Copyright (c) 2017, 2015, 2014, 2013, 2012, 2011 by Chris Weyl.
583              
584             This is free software, licensed under:
585              
586             The GNU Lesser General Public License, Version 2.1, February 1999
587              
588             =cut