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