fbdfd9b0d37df227a98c713a4a2ede41e66ee799
[gitmo/MooseX-AttributeHelpers.git] / lib / MooseX / AttributeHelpers / Trait / Base.pm
1
2 package MooseX::AttributeHelpers::Trait::Base;
3 use Moose::Role;
4 use Moose::Util::TypeConstraints;
5
6 our $VERSION   = '0.21';
7 $VERSION = eval $VERSION;
8 our $AUTHORITY = 'cpan:STEVAN';
9
10 requires 'helper_type';
11
12 # this is the method map you define ...
13 has 'provides' => (
14     is      => 'ro',
15     isa     => 'HashRef',
16     default => sub {{}}
17 );
18
19 has 'curries' => (
20     is      => 'ro',
21     isa     => 'HashRef',
22     default => sub {{}}
23 );
24
25 # these next two are the possible methods
26 # you can use in the 'provides' map.
27
28 # provide a Class or Role which we can
29 # collect the method providers from
30
31 # requires_attr 'method_provider'
32
33 # or you can provide a HASH ref of anon subs
34 # yourself. This will also collect and store
35 # the methods from a method_provider as well
36 has 'method_constructors' => (
37     is      => 'ro',
38     isa     => 'HashRef',
39     lazy    => 1,
40     default => sub {
41         my $self = shift;
42         return +{} unless $self->has_method_provider;
43         # or grab them from the role/class
44         my $method_provider = $self->method_provider->meta;
45         return +{
46             map {
47                 $_ => $method_provider->get_method($_)
48             } $method_provider->get_method_list
49         };
50     },
51 );
52
53 # extend the parents stuff to make sure
54 # certain bits are now required ...
55 has '+default'         => (required => 1);
56 has '+type_constraint' => (required => 1);
57
58 ## Methods called prior to instantiation
59
60 sub process_options_for_provides {
61     my ($self, $options) = @_;
62
63     if (my $type = $self->helper_type) {
64         (exists $options->{isa})
65             || confess "You must define a type with the $type metaclass";
66
67         my $isa = $options->{isa};
68
69         unless (blessed($isa) && $isa->isa('Moose::Meta::TypeConstraint')) {
70             $isa = Moose::Util::TypeConstraints::find_or_create_type_constraint($isa);
71         }
72
73         ($isa->is_a_type_of($type))
74             || confess "The type constraint for a $type ($options->{isa}) must be a subtype of $type";
75     }
76 }
77
78 before '_process_options' => sub {
79     my ($self, $name, $options) = @_;
80     $self->process_options_for_provides($options, $name);
81 };
82
83 ## methods called after instantiation
84
85 sub check_provides_values {
86     my $self = shift;
87
88     my $method_constructors = $self->method_constructors;
89
90     foreach my $key (keys %{$self->provides}) {
91         (exists $method_constructors->{$key})
92             || confess "$key is an unsupported method type";
93     }
94
95     foreach my $key (keys %{$self->curries}) {
96         (exists $method_constructors->{$key})
97             || confess "$key is an unsupported method type";
98     }
99 }
100
101 sub _curry {
102     my $self = shift;
103     my $code = shift;
104
105     my @args = @_;
106     return sub {
107         my $self = shift;
108         $code->($self, @args, @_)
109     };
110 }
111
112 sub _curry_sub {
113     my $self = shift;
114     my $body = shift;
115     my $code = shift;
116
117     return sub {
118         my $self = shift;
119         $code->($self, $body, @_)
120     };
121 }
122
123 after 'install_accessors' => sub {
124     my $attr  = shift;
125     my $class = $attr->associated_class;
126
127     # grab the reader and writer methods
128     # as well, this will be useful for
129     # our method provider constructors
130     my $attr_reader = $attr->get_read_method_ref;
131     my $attr_writer = $attr->get_write_method_ref;
132
133
134     # before we install them, lets
135     # make sure they are valid
136     $attr->check_provides_values;
137
138     my $method_constructors = $attr->method_constructors;
139
140     my $class_name = $class->name;
141
142     while (my ($constructor, $constructed) = each %{$attr->curries}) {
143         my $method_code;
144         while (my ($curried_name, $curried_arg) = each(%$constructed)) {
145             if ($class->has_method($curried_name)) {
146                 confess
147                     "The method ($curried_name) already ".
148                     "exists in class (" . $class->name . ")";
149             }
150             my $body = $method_constructors->{$constructor}->(
151                        $attr,
152                        $attr_reader,
153                        $attr_writer,
154             );
155
156             if (ref $curried_arg eq 'ARRAY') {
157                 $method_code = $attr->_curry($body, @$curried_arg);
158             }
159             elsif (ref $curried_arg eq 'CODE') {
160                 $method_code = $attr->_curry_sub($body, $curried_arg);
161             }
162             else {
163                 confess "curries parameter must be ref type ARRAY or CODE";
164             }
165
166             my $method = MooseX::AttributeHelpers::Meta::Method::Curried->wrap(
167                 $method_code,
168                 package_name => $class_name,
169                 name => $curried_name,
170             );
171
172             $attr->associate_method($method);
173             $class->add_method($curried_name => $method);
174         }
175     }
176
177     foreach my $key (keys %{$attr->provides}) {
178
179         my $method_name = $attr->provides->{$key};
180
181         if ($class->has_method($method_name)) {
182             confess "The method ($method_name) already exists in class (" . $class->name . ")";
183         }
184
185         my $method = MooseX::AttributeHelpers::Meta::Method::Provided->wrap(
186             $method_constructors->{$key}->(
187                 $attr,
188                 $attr_reader,
189                 $attr_writer,
190             ),
191             package_name => $class_name,
192             name => $method_name,
193         );
194         
195         $attr->associate_method($method);
196         $class->add_method($method_name => $method);
197     }
198 };
199
200 after 'remove_accessors' => sub {
201     my $attr  = shift;
202     my $class = $attr->associated_class;
203
204     # provides accessors
205     foreach my $key (keys %{$attr->provides}) {
206         my $method_name = $attr->provides->{$key};
207         my $method = $class->get_method($method_name);
208         $class->remove_method($method_name)
209             if blessed($method) &&
210                $method->isa('MooseX::AttributeHelpers::Meta::Method::Provided');
211     }
212
213     # curries accessors
214     foreach my $key (keys %{$attr->curries}) {
215         my $method_name = $attr->curries->{$key};
216         my $method = $class->get_method($method_name);
217         $class->remove_method($method_name)
218             if blessed($method) &&
219                $method->isa('MooseX::AttributeHelpers::Meta::Method::Provided');
220     }
221 };
222
223 no Moose::Role;
224 no Moose::Util::TypeConstraints;
225
226 1;
227
228 __END__
229
230 =head1 NAME
231
232 MooseX::AttributeHelpers::Trait::Base - base role for helpers
233
234 =head1 METHODS
235
236 =head2 check_provides_values
237
238 Confirms that provides (and curries) has all valid possibilities in it.
239
240 =head2 process_options_for_provides
241
242 Ensures that the type constraint (C<isa>) matches the helper type.
243
244 =head1 BUGS
245
246 All complex software has bugs lurking in it, and this module is no 
247 exception. If you find a bug please either email me, or add the bug
248 to cpan-RT.
249
250 =head1 AUTHORS
251
252 Yuval Kogman
253
254 Shawn M Moore
255
256 Jesse Luehrs
257
258 =head1 COPYRIGHT AND LICENSE
259
260 Copyright 2007-2009 by Infinity Interactive, Inc.
261
262 L<http://www.iinteractive.com>
263
264 This library is free software; you can redistribute it and/or modify
265 it under the same terms as Perl itself.
266
267 =cut