File Coverage

lib/MooseX/Extended.pm
Criterion Covered Total %
statement 81 86 94.1
branch 17 22 77.2
condition 4 6 66.6
subroutine 17 17 100.0
pod 0 1 0.0
total 119 132 90.1


line stmt bran cond sub pod time code
1             package MooseX::Extended;
2              
3             # ABSTRACT: Extend Moose with safe defaults and useful features
4              
5 19     19   4087283 use 5.20.0;
  19         201  
6 19     19   96 use warnings;
  19         32  
  19         491  
7              
8 19     19   8172 use Moose::Exporter;
  19         1882108  
  19         131  
9 19     19   8905 use Moose ();
  19         4962779  
  19         628  
10 19     19   9872 use MooseX::StrictConstructor ();
  19         509794  
  19         491  
11 19     19   140 use mro ();
  19         54  
  19         273  
12 19     19   86 use namespace::autoclean ();
  19         46  
  19         385  
13 19     19   1561 use Module::Load 'load';
  19         3279  
  19         163  
14 19         1887 use MooseX::Extended::Core qw(
15             _assert_import_list_is_valid
16             _debug
17             _disabled_warnings
18             _enabled_features
19             _our_import
20             _our_init_meta
21             field
22             param
23 19     19   9417 );
  19         67  
24 19     19   181 use feature _enabled_features();
  19         47  
  19         70  
25 19     19   138 no warnings _disabled_warnings();
  19         39  
  19         80  
26 19     19   8738 use B::Hooks::AtRuntime 'after_runtime';
  19         40295  
  19         156  
27 19     19   6363 use Import::Into;
  19         1240  
  19         12551  
28              
29             our $VERSION = '0.35';
30              
31             sub import {
32 47     47   96754 my ( $class, %args ) = @_;
33 47         223 my @caller = caller(0);
34 47         596 $args{_import_type} = 'class';
35 47         162 $args{_caller_eval} = ( $caller[1] =~ /^\(eval/ ); # https://github.com/Ovid/moosex-extended/pull/34
36 47         174 my $target_class = _assert_import_list_is_valid( $class, \%args );
37 46         124 my @with_meta = grep { not $args{excludes}{$_} } qw(field param);
  92         282  
38 46 50       150 if (@with_meta) {
39 46         138 @with_meta = ( with_meta => [@with_meta] );
40             }
41 46         372 my ( $import, undef, undef ) = Moose::Exporter->setup_import_methods(
42             @with_meta,
43             install => [qw/unimport/],
44             also => ['Moose'],
45             );
46 46         53714 _our_import( $class, $import, $target_class );
47             }
48              
49             # Internal method setting up exports. No public
50             # documentation by design
51 46     46 0 4144 sub init_meta ( $class, %params ) {
  46         135  
  46         146  
  46         75  
52 46         2053 Moose->init_meta(%params);
53 46         200839 _our_init_meta( $class, \&_apply_default_features, %params );
54             }
55              
56             # XXX we don't actually use the $params here, even though we need it for
57             # MooseX::Extended::Role. But we need to declare it in the signature to make
58             # this code work
59 46     46   81 sub _apply_default_features ( $config, $for_class, $params = undef ) {
  46         72  
  46         73  
  46         72  
  46         71  
60 46 100       158 if ( my $types = $config->{types} ) {
61 15         131 _debug("$for_class: importing types '@$types'");
62 15         176 MooseX::Extended::Types->import::into( $for_class, @$types );
63             }
64              
65 46 100       150739 MooseX::StrictConstructor->import( { into => $for_class } ) unless $config->{excludes}{StrictConstructor};
66 46 100       212006 Carp->import::into($for_class) unless $config->{excludes}{carp};
67 46 50       11653 namespace::autoclean->import::into($for_class) unless $config->{excludes}{autoclean};
68              
69 46 100 66     9699 unless ( $config->{excludes}{immutable} or $config->{_caller_eval} ) { # https://github.com/Ovid/moosex-extended/pull/34
70              
71             # after_runtime is loaded too late under the debugger
72             eval {
73 24         138 load B::Hooks::AtRuntime, 'after_runtime';
74             after_runtime {
75 22     22   72060 $for_class->meta->make_immutable;
76 22 50       452549 if ( $config->{debug} ) {
77              
78             # they're doing debug on a class-by-class basis, so
79             # turn this off after the class compiles
80 0         0 $MooseX::Extended::Debug = 0;
81             }
82 24         8151 };
83 24         1668 1;
84 24 50       63 } or do {
85 0         0 my $error = $@;
86 0         0 warn
87             "Could not load 'B::Hooks::AtRuntime': $error. You class is not immutable. You can `use MooseX::Extended excludes => ['immutable'];` to suppress this warning.";
88             };
89             }
90 46 100 66     351 unless ( $config->{excludes}{true} or $config->{_caller_eval} ) { # https://github.com/Ovid/moosex-extended/pull/34
91             eval {
92 24         92 load true;
93 24         53507 true->import::into($for_class); # no need for `1` at the end of the module
94 24         40818 1;
95 24 50       110 } or do {
96 0         0 my $error = $@;
97 0         0 warn
98             "Could not load 'true': $error. Your class must end in a true value. You can `use MooseX::Extended excludes => ['true'];` to suppress this warning.";
99             };
100             }
101              
102             # If we never use multiple inheritance, this should not be needed.
103             mro::set_mro( $for_class, 'c3' )
104 46 100       291 unless $config->{excludes}{c3};
105              
106 46         181 feature->import( _enabled_features() );
107 46         266 warnings->unimport(_disabled_warnings);
108             }
109             1;
110              
111             __END__
112              
113             =pod
114              
115             =encoding UTF-8
116              
117             =head1 NAME
118              
119             MooseX::Extended - Extend Moose with safe defaults and useful features
120              
121             =head1 VERSION
122              
123             version 0.35
124              
125             =head1 SYNOPSIS
126              
127             package My::Names {
128             use MooseX::Extended types => [qw(compile Num NonEmptyStr Str PositiveInt ArrayRef)];
129             use List::Util 'sum';
130              
131             # the distinction between `param` and `field` makes it easier to
132             # see which are available to `new`
133             param _name => ( isa => NonEmptyStr, init_arg => 'name' );
134             param title => ( isa => Str, required => 0 );
135              
136             # forbidden in the constructor
137             field created => ( isa => PositiveInt, default => sub {time} );
138              
139             sub name ($self) {
140             my $title = $self->title;
141             my $name = $self->_name;
142             return $title ? "$title $name" : $name;
143             }
144              
145             sub add ( $self, $args ) {
146             state $check = compile( ArrayRef [ Num, 1 ] ); # at least one number
147             ($args) = $check->($args);
148             return sum( $args->@* );
149             }
150              
151             sub warnit ($self) {
152             carp("this is a warning");
153             }
154             }
155              
156             =head1 DESCRIPTION
157              
158             This module is B<BETA> code. It's feature-complete for release and has no
159             known bugs. We believe it's ready for production, but make no promises.
160              
161             This is a quick overview. See L<MooseX::Extended::Manual::Tutorial> for more
162             information.
163              
164             This class attempts to create a safer version of Moose that defaults to
165             read-only attributes and is easier to read and write.
166              
167             It tries to bring some of the lessons learned from L<the Corinna
168             project|https://github.com/Ovid/Cor>, while acknowledging that you can't
169             always get what you want (such as true encapsulation and true methods).
170              
171             This:
172              
173             package My::Class {
174             use MooseX::Extended;
175              
176             ... your code here
177             }
178              
179             Is sort of the equivalent to:
180              
181             package My::Class {
182             use v5.20.0;
183             use Moose;
184             use MooseX::StrictConstructor;
185             use feature qw( signatures postderef postderef_qq );
186             no warnings qw( experimental::signatures experimental::postderef );
187             use namespace::autoclean;
188             use Carp;
189             use mro 'c3';
190              
191             ... your code here
192              
193             __PACKAGE__->meta->make_immutable;
194             }
195             1;
196              
197             It also exports two functions which are similar to Moose C<has>: C<param> and
198             C<field>.
199              
200             A C<param> is a required parameter (defaults may be used). A C<field> is not
201             intended to be passed to the constructor.
202              
203             B<Note>: the C<has> function is still available, even if it's not needed.
204             Unlike C<param> and C<field>, it still requires an C<is> option.
205              
206             Also, while your author likes the postfix block syntax, it's not required. You
207             can even safely inline multiple packages in the same file:
208              
209             package My::Point;
210             use MooseX::Extended types => 'Num';
211              
212             param [ 'x', 'y' ] => ( isa => Num );
213              
214             package My::Point::Mutable;
215             use MooseX::Extended;
216             extends 'My::Point';
217              
218             param [ '+x', '+y' ] => ( writer => 1, clearer => 1, default => 0 );
219              
220             sub invert ($self) {
221             my ( $x, $y ) = ( $self->x, $self->y );
222             $self->set_x($y);
223             $self->set_y($x);
224             }
225              
226             # MooseX::Extended will cause this to return true, even if we try to return
227             # false
228             0;
229              
230             =head1 CONFIGURATION
231              
232             You may pass an import list to L<MooseX::Extended>.
233              
234             use MooseX::Extended
235             excludes => [qw/StrictConstructor carp/], # I don't want these features
236             types => [qw/compile PositiveInt HashRef/]; # I want these type tools
237              
238             =head2 C<types>
239              
240             Allows you to import any types provided by L<MooseX::Extended::Types>.
241              
242             This:
243              
244             use MooseX::Extended::Role types => [qw/compile PositiveInt HashRef/];
245              
246             Is identical to this:
247              
248             use MooseX::Extended::Role;
249             use MooseX::Extended::Types qw( compile PositiveInt HashRef );
250              
251             =head2 C<excludes>
252              
253             You may find some features to be annoying, or even cause potential bugs (e.g.,
254             if you have a C<croak> method, our importing of C<Carp::croak> will be a
255             problem.
256              
257             A single argument to C<excludes> can be a string. Multiple C<excludes> require
258             an array reference:
259              
260             use MooseX::Extended excludes => [qw/StrictConstructor autoclean/];
261              
262             You can exclude the following:
263              
264             =over 4
265              
266             =item * C<StrictConstructor>
267              
268             use MooseX::Extended excludes => 'StrictConstructor';
269              
270             Excluding this will no longer import C<MooseX::StrictConstructor>.
271              
272             =item * C<autoclean>
273              
274             use MooseX::Extended excludes => 'autoclean';
275              
276             Excluding this will no longer import C<namespace::autoclean>.
277              
278             =item * C<c3>
279              
280             use MooseX::Extended excludes => 'c3';
281              
282             Excluding this will no longer apply the C3 mro.
283              
284             =item * C<carp>
285              
286             use MooseX::Extended excludes => 'carp';
287              
288             Excluding this will no longer import C<Carp::croak> and C<Carp::carp>.
289              
290             =item * C<immutable>
291              
292             use MooseX::Extended excludes => 'immutable';
293              
294             Excluding this will no longer make your class immutable.
295              
296             =item * C<true>
297              
298             use MooseX::Extended excludes => 'true';
299              
300             Excluding this will require your module to end in a true value.
301              
302             =item * C<param>
303              
304             use MooseX::Extended excludes => 'param';
305              
306             Excluding this will make the C<param> function unavailable.
307              
308             =item * C<field>
309              
310             use MooseX::Extended excludes => 'field';
311              
312             Excluding this will make the C<field> function unavailable.
313              
314             =back
315              
316             =head2 C<includes>
317              
318             Several I<optional> features of L<MooseX::Extended> make this module much more
319             powerful. For example, to include try/catch and a C<method> keyword:
320              
321             use MooseX::Extended includes => [ 'method', 'try' ];
322              
323             A single argument to C<includes> can be a string. Multiple C<includes> require
324             an array reference:
325              
326             use MooseX::Extended includes => [qw/method try/];
327              
328             See L<MooseX::Extended::Manual::Includes> for more information.
329              
330             =head1 REDUCING BOILERPLATE
331              
332             Let's say you've settled on the following feature set:
333              
334             use MooseX::Extended
335             excludes => [qw/StrictConstructor carp/],
336             includes => 'method',
337             types => ':Standard';
338              
339             And you keep typing that over and over. We've removed a lot of boilerplate,
340             but we've added different boilerplate. Instead, just create
341             C<My::Custom::Moose> and C<use My::Custom::Moose;>. See
342             L<MooseX::Extended::Custom> for details.
343              
344             =head1 IMMUTABILITY
345              
346             =head2 Making Your Class Immutable
347              
348             You no longer need to end your Moose classes with:
349              
350             __PACKAGE__->meta->make_immutable;
351              
352             That prevents further changes to the class and provides some optimizations to
353             make the code run much faster. However, it's somewhat annoying to type. We do
354             this for you, via L<B::Hooks::AtRuntime>. You no longer need to do this yourself.
355              
356             =head2 Making Your Instance Immutable
357              
358             By default, attributes defined via C<param> and C<field> are read-only.
359             However, if they contain a reference, you can fetch the reference, mutate it,
360             and now everyone with a copy of that reference has mutated state.
361              
362             To handle that, we offer a new C<< clone => $clone_type >> pair for attributes.
363              
364             See the L<MooseX::Extended::Manual::Cloning> documentation.
365              
366             =head1 OBJECT CONSTRUCTION
367              
368             Object construction for L<MooseX::Extended> is identical to Moose because
369             MooseX::Extended I<is> Moose, so no changes are needed. However, in addition
370             to C<has>, we also provide C<param> and C<field> attributes, both of which are
371             C<< is => 'ro' >> by default.
372              
373             The C<param> is I<required>, whether by passing it to the constructor, or using
374             C<default> or C<builder>.
375              
376             The C<field> is I<forbidden> in the constructor and is lazy if it has a
377             builder, because that builder is often dependent on attributes set in the
378             constructor (and why call it if it's not used?).
379              
380             Here's a short example:
381              
382             package Class::Name {
383             use MooseX::Extended types => [qw(compile Num NonEmptyStr Str)];
384              
385             # these default to 'ro' (but you can override that) and are required
386             param _name => ( isa => NonEmptyStr, init_arg => 'name' );
387             param title => ( isa => Str, required => 0 );
388              
389             # fields must never be passed to the constructor
390             # note that ->title and ->name are guaranteed to be set before
391             # this because fields are lazy by default
392             field name => (
393             isa => NonEmptyStr,
394             default => sub ($self) {
395             my $title = $self->title;
396             my $name = $self->_name;
397             return $title ? "$title $name" : $name;
398             },
399             );
400             }
401              
402             See L<MooseX::Extended::Manual::Construction> for a full explanation.
403              
404             =head1 ATTRIBUTE SHORTCUTS
405              
406             When using C<field> or C<param>, we have some attribute shortcuts:
407              
408             param name => (
409             isa => NonEmptyStr,
410             writer => 1, # set_name
411             reader => 1, # get_name
412             predicate => 1, # has_name
413             clearer => 1, # clear_name
414             builder => 1, # _build_name
415             );
416              
417             sub _build_name ($self) {
418             ...
419             }
420              
421             You can also do this:
422              
423             param name ( isa => NonEmptyStr, builder => sub {...} );
424              
425             That's the same as:
426              
427             param name ( isa => NonEmptyStr, builder => '_build_name' );
428              
429             sub _build_name {...}
430              
431             See L<MooseX::Extended::Manual::Shortcuts> for a full explanation.
432              
433             =head1 INVALID ATTRIBUTE DEFINITIONS
434              
435             The following L<Moose> code will print C<WhoAmI>. However, the second attribute
436             name is clearly invalid.
437              
438             package Some::Class {
439             use Moose;
440              
441             has name => ( is => 'ro' );
442             has '-bad' => ( is => 'ro' );
443             }
444              
445             my $object = Some::Class->new( name => 'WhoAmI' );
446             say $object->name;
447              
448             C<MooseX::Extended> will throw a
449             L<Moose::Exception::InvalidAttributeDefinition> exception if it encounters an
450             illegal method name for an attribute.
451              
452             This also applies to various attributes which allow method names, such as
453             C<clone>, C<builder>, C<clearer>, C<writer>, C<reader>, and C<predicate>.
454              
455             Trying to pass a defined C<init_arg> to C<field> will also throw this
456             exception, unless the init_arg begins with an underscore. (It is sometimes
457             useful to be able to define an C<init_arg> for unit testing.)
458              
459             =head1 BUGS AND LIMITATIONS
460              
461             None known at this time.
462              
463             =head1 MANUAL
464              
465             =over 4
466              
467             =item * L<MooseX::Extended::Manual::Tutorial>
468              
469             =item * L<MooseX::Extended::Manual::Overview>
470              
471             =item * L<MooseX::Extended::Manual::Construction>
472              
473             =item * L<MooseX::Extended::Manual::Includes>
474              
475             =item * L<MooseX::Extended::Manual::Shortcuts>
476              
477             =item * L<MooseX::Extended::Manual::Cloning>
478              
479             =back
480              
481             =head1 RELATED MODULES
482              
483             =over 4
484              
485             =item * L<MooseX::Extended::Types> is included in the distribution.
486              
487             This provides core types for you.
488              
489             =item * L<MooseX::Extended::Role> is included in the distribution.
490              
491             C<MooseX::Extended>, but for roles.
492              
493             =back
494              
495             =head1 TODO
496              
497             Some of this may just be wishful thinking. Some of this would be interesting if
498             others would like to collaborate.
499              
500             =head2 Configurable Types
501              
502             We provide C<MooseX::Extended::Types> for convenience, along with the C<declare>
503             function. We should write up (and test) examples of extending it.
504              
505             =head2 C<BEGIN::Lift>
506              
507             This idea maybe belongs in C<MooseX::Extended::OverKill>, but ...
508              
509             Quite often you see things like this:
510              
511             BEGIN { extends 'Some::Parent' }
512              
513             Or this:
514              
515             sub serial_number; # required by a role, must be compile-time
516             has serial_number => ( ... );
517              
518             In fact, there are a variety of Moose functions which would work better if
519             they ran at compile-time instead of runtime, making them look a touch more
520             like native functions. My various attempts at solving this have failed, but I
521             confess I didn't try too hard.
522              
523             =head1 NOTES
524              
525             There are a few things you might be interested to know about this module when
526             evaluating it.
527              
528             Most of this is written with bog-standard L<Moose>, so there's nothing
529             terribly weird inside, but you may wish to note that we use
530             L<B::Hooks::AtRuntime> and L<true>. They seem sane, but I<caveat emptor>.
531              
532             =head1 SEE ALSO
533              
534             =over 4
535              
536             =item * L<Corinna|https://github.com/Ovid/Cor>
537              
538             The RFC of the new version of OOP planned for the Perl core.
539              
540             =item * L<MooseX::Modern|https://metacpan.org/pod/MooseX::Modern>
541              
542             MooseX::Modern - Precision classes for Modern Perl
543              
544             =item * L<Zydeco|https://metacpan.org/pod/Zydeco>
545              
546             Zydeco - Jazz up your Perl
547              
548             =item * L<Dios|https://metacpan.org/pod/Dios>
549              
550             Dios - Declarative Inside-Out Syntax
551              
552             =item * L<MooseX::AttributeShortcuts|https://metacpan.org/pod/MooseX::AttributeShortcuts>
553              
554             MooseX::AttributeShortcuts - Shorthand for common attribute options
555              
556             =back
557              
558             =head1 AUTHOR
559              
560             Curtis "Ovid" Poe <curtis.poe@gmail.com>
561              
562             =head1 COPYRIGHT AND LICENSE
563              
564             This software is Copyright (c) 2022 by Curtis "Ovid" Poe.
565              
566             This is free software, licensed under:
567              
568             The Artistic License 2.0 (GPL Compatible)
569              
570             =cut