Merge branch 'master' into traits
[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.17';
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 # this confirms that provides (and curries) has
86 # all valid possibilities in it
87 sub check_provides_values {
88     my $self = shift;
89
90     my $method_constructors = $self->method_constructors;
91
92     foreach my $key (keys %{$self->provides}) {
93         (exists $method_constructors->{$key})
94             || confess "$key is an unsupported method type";
95     }
96
97     foreach my $key (keys %{$self->curries}) {
98         (exists $method_constructors->{$key})
99             || confess "$key is an unsupported method type";
100     }
101 }
102
103 sub _curry {
104     my $self = shift;
105     my $code = shift;
106
107     my @args = @_;
108     return sub {
109         my $self = shift;
110         $code->($self, @args, @_)
111     };
112 }
113
114 sub _curry_sub {
115     my $self = shift;
116     my $body = shift;
117     my $code = shift;
118
119     return sub {
120         my $self = shift;
121         $code->($self, $body, @_)
122     };
123 }
124
125 after 'install_accessors' => sub {
126     my $attr  = shift;
127     my $class = $attr->associated_class;
128
129     # grab the reader and writer methods
130     # as well, this will be useful for
131     # our method provider constructors
132     my $attr_reader = $attr->get_read_method_ref;
133     my $attr_writer = $attr->get_write_method_ref;
134
135
136     # before we install them, lets
137     # make sure they are valid
138     $attr->check_provides_values;
139
140     my $method_constructors = $attr->method_constructors;
141
142     my $class_name = $class->name;
143
144     while (my ($constructor, $constructed) = each %{$attr->curries}) {
145         my $method_code;
146         while (my ($curried_name, $curried_arg) = each(%$constructed)) {
147             if ($class->has_method($curried_name)) {
148                 confess
149                     "The method ($curried_name) already ".
150                     "exists in class (" . $class->name . ")";
151             }
152             my $body = $method_constructors->{$constructor}->(
153                        $attr,
154                        $attr_reader,
155                        $attr_writer,
156             );
157
158             if (ref $curried_arg eq 'ARRAY') {
159                 $method_code = $attr->_curry($body, @$curried_arg);
160             }
161             elsif (ref $curried_arg eq 'CODE') {
162                 $method_code = $attr->_curry_sub($body, $curried_arg);
163             }
164             else {
165                 confess "curries parameter must be ref type HASH or CODE";
166             }
167
168             my $method = MooseX::AttributeHelpers::Meta::Method::Curried->wrap(
169                 $method_code,
170                 package_name => $class_name,
171                 name => $curried_name,
172             );
173                 
174             $attr->associate_method($method);
175             $class->add_method($curried_name => $method);
176         }
177     }
178
179     foreach my $key (keys %{$attr->provides}) {
180
181         my $method_name = $attr->provides->{$key};
182
183         if ($class->has_method($method_name)) {
184             confess "The method ($method_name) already exists in class (" . $class->name . ")";
185         }
186
187         my $method = MooseX::AttributeHelpers::Meta::Method::Provided->wrap(
188             $method_constructors->{$key}->(
189                 $attr,
190                 $attr_reader,
191                 $attr_writer,
192             ),
193             package_name => $class_name,
194             name => $method_name,
195         );
196         
197         $attr->associate_method($method);
198         $class->add_method($method_name => $method);
199     }
200 };
201
202 after 'remove_accessors' => sub {
203     my $attr  = shift;
204     my $class = $attr->associated_class;
205
206     # provides accessors
207     foreach my $key (keys %{$attr->provides}) {
208         my $method_name = $attr->provides->{$key};
209         my $method = $class->get_method($method_name);
210         $class->remove_method($method_name)
211             if blessed($method) &&
212                $method->isa('MooseX::AttributeHelpers::Meta::Method::Provided');
213     }
214
215     # curries accessors
216     foreach my $key (keys %{$attr->curries}) {
217         my $method_name = $attr->curries->{$key};
218         my $method = $class->get_method($method_name);
219         $class->remove_method($method_name)
220             if blessed($method) &&
221                $method->isa('MooseX::AttributeHelpers::Meta::Method::Provided');
222     }
223 };
224
225 no Moose::Role;
226 no Moose::Util::TypeConstraints;
227
228 1;
229