File Coverage

blib/lib/MooseX/Role/TraitConstructor.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package MooseX::Role::TraitConstructor;
4 1     1   26342 use Moose::Role;
  0            
  0            
5              
6             use List::Util ();
7              
8             our $VERSION = "0.01";
9              
10             use constant constructor_trait_param => "traits";
11              
12             sub new_with_traits {
13             my ( $class, @opts ) = @_;
14              
15             my %params;
16              
17             if (scalar @opts == 1) {
18             if (defined $opts[0]) {
19             (ref($opts[0]) eq 'HASH')
20             || confess "Single parameters to new() must be a HASH ref";
21             %params = %{$opts[0]};
22             }
23             }
24             else {
25             %params = @opts;
26             }
27              
28             $class->interpolate_class_from_params(\%params)->new(%params);
29             }
30              
31             sub interpolate_class_from_params {
32             my ($class, $params) = @_;
33              
34             $class = ref($class) || $class;
35              
36             my @traits;
37              
38             if (my $traits = delete $params->{$class->constructor_trait_param($params)}) {
39             if ( @traits = $class->process_constructor_traits($params, @$traits) ) {
40             my $anon_class = Moose::Meta::Class->create_anon_class(
41             superclasses => [ $class ],
42             roles => [ @traits ],
43             cache => 1,
44             );
45              
46             $class = $anon_class->name;
47             }
48             }
49              
50             return ( wantarray ? ( $class, @traits ) : $class );
51             }
52              
53             sub process_constructor_traits {
54             my ( $class, $params, @traits ) = @_;
55              
56             $class->filter_constructor_traits( $params, $class->resolve_constructor_traits( $params, @traits ) );
57              
58             }
59              
60             sub resolve_constructor_traits {
61             my ( $class, $params, @traits ) = @_;
62              
63             my $root = $class->guess_original_class_name($params);
64              
65             map { $class->resolve_constructor_trait($params, $root, $_) } @traits;
66             }
67              
68             sub guess_original_class_name {
69             my ( $class, $params ) = @_;
70              
71             my $meta = $class->meta;
72              
73             if ( $meta->is_anon_class ) {
74             if ( my $root = List::Util::first(sub { not $_->meta->is_anon_class }, $meta->linearized_isa ) ) {
75             return $root;
76             }
77             }
78              
79             return $class;
80             }
81              
82             sub resolve_constructor_trait {
83             my ( $class, $params, $possible_root, $trait ) = @_;
84              
85             if ( ref $trait ) {
86             return $trait->anme;
87             } else {
88             my $processed_trait;
89              
90             {
91             local $@;
92             if ( $processed_trait = $class->process_trait_name($trait, $params, $possible_root) ) {
93             if ( eval { Class::MOP::load_class($processed_trait); 1 } ) {
94             return $processed_trait;
95             }
96             }
97              
98             if ( eval { Class::MOP::load_class($trait); 1 } ) {
99             return $trait;
100             }
101             }
102              
103             require Carp;
104             Carp::croak("Couldn't load $trait" . ( $processed_trait ? " or $processed_trait" : "" ) . " to mix in with $class" . ( $class->meta->is_anon_class ? " ($possible_root)" : "" ));
105             }
106             }
107              
108             sub process_trait_name {
109             my ( $class, $trait, $params, $possible_root) = @_;
110              
111             return join "::", $possible_root, $trait;
112             }
113              
114             sub filter_constructor_traits {
115             my ( $class, $params, @traits ) = @_;
116              
117             return grep { not $class->does($_) } @traits;
118             }
119              
120             __PACKAGE__
121              
122             __END__
123              
124             =pod
125              
126             =head1 NAME
127              
128             MooseX::Role::TraitConstructor - A wrapper for C<new> that can accept a
129             C<traits> parameter.
130              
131             =head1 SYNOPSIS
132              
133             package Foo;
134             use Moose;
135              
136             with qw(MooseX::Role::TraitConstructor);
137              
138              
139             package Foo::Bah;
140              
141             sub bah_method { ... }
142              
143              
144              
145             my $foo = Foo->new( traits => [qw( Bah )] );
146              
147             $foo->bah_method;
148              
149             =head1 DESCRIPTION
150              
151             This role allows you to easily accept a C<traits> argument (or another name)
152             into your constructor, which will easily mix roles into an anonymous class
153             before construction, much like L<Moose::Meta::Attribute> does.
154              
155             =head1 METHODS
156              
157             =over 4
158              
159             =item constructor_trait_param
160              
161             Returns the string C<traits>.
162              
163             Override to rename the parameter.
164              
165             =item new_with_traits %params
166              
167             =item new_with_traits $params
168              
169             A L<Moose::Object/new> like parameter processor which will call C<new> on the
170             return value of C<interpolate_class_from_params>.
171              
172             =item interpolate_class_from_params $params
173              
174             This method will automatically create an anonymous class with the roles from
175             the C<traits> param mixed into it if one exists.
176              
177             If not the normal class name will be returned.
178              
179             Will remove the C<traits> parameter from C<$params>.
180              
181             Also works as an instance method, but always returns a class name.
182              
183             In list context also returns the actual list of roles mixed into the class.
184              
185             =item process_constructor_traits $params, @traits
186              
187             Calls C<filter_constructor_traits> on the result of C<resolve_constructor_traits>.
188              
189             =item resolve_constructor_traits $params, @traits
190              
191             Attempt to load the traits specified in C<@traits> usinc C<resolve_constructor_trait>
192              
193             =item guess_original_class_name $params
194              
195             =item resolve_constructor_trait $params, $possible_root, $trait
196              
197             Attempts to get a processed name from C<process_trait_name>, and then tries to load that.
198              
199             If C<process_trait_name> didn't return a true value or its return value could
200             not be loaded then C<$trait> will be tried.
201              
202             If nothing could be loaded an error is thrown.
203              
204             C<$possible_root> is the name of the first non anonymous class in the
205             C<linearized_isa>, usually C<$class>, but will DWIM in case C<$class> has
206             already been interpolated with traits from a named class.
207              
208             =item process_trait_name $trait, $params, $possible_root
209              
210             Returns C<< join "::", $possible_root, $trait >>.
211              
212             You probably want to override this method.
213              
214             =item filter_constructor_traits $params, $traits,
215              
216             Returns all the the roles that the invocant class doesn't already do (uses
217             C<does>).
218              
219             =back
220              
221             =head1 VERSION CONTROL
222              
223             L<http://code2.0beta.co.uk/moose/svn/>. Ask on #moose for commit bits.
224              
225             =head1 AUTHOR
226              
227             Yuval Kogman E<lt>nothingmuch@woobling.orgE<gt>
228              
229             =head1 COPYRIGHT
230              
231             Copyright (c) 2008 Yuval Kogman. All rights reserved
232             This program is free software; you can redistribute
233             it and/or modify it under the same terms as Perl itself.
234              
235             =cut