b6b4065ea53f7b124975a94f5777925667b98f8f
[gitmo/MooseX-Emulate-Class-Accessor-Fast.git] / lib / MooseX / Emulate / Class / Accessor / Fast.pm
1 package MooseX::Emulate::Class::Accessor::Fast;
2
3 use Moose::Role;
4 use Class::MOP ();
5 use Scalar::Util ();
6
7 use MooseX::Emulate::Class::Accessor::Fast::Meta::Accessor ();
8
9 our $VERSION = '0.00701';
10
11 =head1 NAME
12
13 MooseX::Emulate::Class::Accessor::Fast -
14   Emulate Class::Accessor::Fast behavior using Moose attributes
15
16 =head1 SYNOPSYS
17
18     package MyClass;
19     use Moose;
20
21     with 'MooseX::Emulate::Class::Accessor::Fast';
22
23
24     #fields with readers and writers
25     __PACKAGE__->mk_accessors(qw/field1 field2/);
26     #fields with readers only
27     __PACKAGE__->mk_accessors(qw/field3 field4/);
28     #fields with writers only
29     __PACKAGE__->mk_accessors(qw/field5 field6/);
30
31
32 =head1 DESCRIPTION
33
34 This module attempts to emulate the behavior of L<Class::Accessor::Fast> as
35 accurately as possible using the Moose attribute system. The public API of
36 C<Class::Accessor::Fast> is wholly supported, but the private methods are not.
37 If you are only using the public methods (as you should) migration should be a
38 matter of switching your C<use base> line to a C<with> line.
39
40 While I have attempted to emulate the behavior of Class::Accessor::Fast as closely
41 as possible bugs may still be lurking in edge-cases.
42
43 =head1 BEHAVIOR
44
45 Simple documentation is provided here for your convenience, but for more thorough
46 documentation please see L<Class::Accessor::Fast> and L<Class::Accessor>.
47
48 =head2 A note about introspection
49
50 Please note that, at this time, the C<is> flag attribute is not being set. To
51 determine the C<reader> and C<writer> methods using introspection in later versions
52 of L<Class::MOP> ( > 0.38) please use the C<get_read_method> and C<get_write_method>
53 methods in L<Class::MOP::Attribute>. Example
54
55     # with Class::MOP <= 0.38
56     my $attr = $self->meta->find_attribute_by_name($field_name);
57     my $reader_method = $attr->reader || $attr->accessor;
58     my $writer_method = $attr->writer || $attr->accessor;
59
60     # with Class::MOP > 0.38
61     my $attr = $self->meta->find_attribute_by_name($field_name);
62     my $reader_method = $attr->get_read_method;
63     my $writer_method = $attr->get_write_method;
64
65 =head1 METHODS
66
67 =head2 BUILD $self %args
68
69 Change the default Moose class building to emulate the behavior of C::A::F and
70 store arguments in the instance hashref.
71
72 =cut
73
74 my $locate_metaclass = sub {
75   my $class = Scalar::Util::blessed($_[0]) || $_[0];
76   return Class::MOP::get_metaclass_by_name($class)
77     || Moose::Meta::Class->initialize($class);
78 };
79
80 sub BUILD {
81   my $self = shift;
82   my %args;
83   if (scalar @_ == 1 && defined $_[0] && ref($_[0]) eq 'HASH') {
84     %args = %{$_[0]};
85   } elsif( scalar(@_) ) {
86     %args = @_;
87   }
88   my @extra = grep { !exists($self->{$_}) } keys %args;
89   @{$self}{@extra} = @args{@extra};
90   return $self;
91 }
92
93 =head2 mk_accessors @field_names
94
95 Create read-write accessors. An attribute named C<$field_name> will be created.
96 The name of the c<reader> and C<writer> methods will be determined by the return
97 value of C<accessor_name_for> and C<mutator_name_for>, which by default return the
98 name passed unchanged. If the accessor and mutator names are equal the C<accessor>
99 attribute will be passes to Moose, otherwise the C<reader> and C<writer> attributes
100 will be passed. Please see L<Class::MOP::Attribute> for more information.
101
102 =cut
103
104 sub mk_accessors{
105   my $self = shift;
106   my $meta = $locate_metaclass->($self);
107   my $class = $meta->name;
108   confess("You are trying to modify ${class}, which has been made immutable, this is ".
109     "not supported. Try subclassing ${class}, rather than monkeypatching it")
110     if $meta->is_immutable;
111
112   for my $attr_name (@_){
113     $meta->remove_attribute($attr_name)
114       if $meta->find_attribute_by_name($attr_name);
115     my $reader = $self->accessor_name_for($attr_name);
116     my $writer = $self->mutator_name_for( $attr_name);
117
118     #dont overwrite existing methods
119     if($reader eq $writer){
120       my %opts = ( $meta->has_method($reader) ? () : (accessor => $reader) );
121       my $attr = $meta->find_attribute_by_name($attr_name) || $meta->add_attribute($attr_name, %opts,
122         traits => ['MooseX::Emulate::Class::Accessor::Fast::Meta::Role::Attribute']
123       );
124       if($attr_name eq $reader){
125         my $alias = "_${attr_name}_accessor";
126         next if $meta->has_method($alias);
127         my @alias_method = $attr->process_accessors(accessor => $alias, 0);
128         $meta->add_method(@alias_method);
129       }
130     } else {
131       my @opts = ( $meta->has_method($writer) ? () : (writer => $writer) );
132       push(@opts, (reader => $reader)) unless $meta->has_method($reader);
133       my $attr = $meta->find_attribute_by_name($attr_name) || $meta->add_attribute($attr_name, @opts,
134         traits => ['MooseX::Emulate::Class::Accessor::Fast::Meta::Role::Attribute']
135       );
136     }
137   }
138 }
139
140 =head2 mk_ro_accessors @field_names
141
142 Create read-only accessors.
143
144 =cut
145
146 sub mk_ro_accessors{
147   my $self = shift;
148   my $meta = $locate_metaclass->($self);
149   my $class = $meta->name;
150   confess("You are trying to modify ${class}, which has been made immutable, this is ".
151     "not supported. Try subclassing ${class}, rather than monkeypatching it")
152     if $meta->is_immutable;
153   for my $attr_name (@_){
154     $meta->remove_attribute($attr_name)
155       if $meta->find_attribute_by_name($attr_name);
156     my $reader = $self->accessor_name_for($attr_name);
157     my @opts = ($meta->has_method($reader) ? () : (reader => $reader) );
158     my $attr = $meta->add_attribute($attr_name, @opts,
159       traits => ['MooseX::Emulate::Class::Accessor::Fast::Meta::Role::Attribute']
160     ) if scalar(@opts);
161     if($reader eq $attr_name && $reader eq $self->mutator_name_for($attr_name)){
162       $meta->add_method("_${attr_name}_accessor" => $attr->get_read_method_ref)
163         unless $meta->has_method("_${attr_name}_accessor");
164     }
165   }
166 }
167
168 =head2 mk_ro_accessors @field_names
169
170 Create write-only accessors.
171
172 =cut
173
174 #this is retarded.. but we need it for compatibility or whatever.
175 sub mk_wo_accessors{
176   my $self = shift;
177   my $meta = $locate_metaclass->($self);
178   my $class = $meta->name;
179   confess("You are trying to modify ${class}, which has been made immutable, this is ".
180     "not supported. Try subclassing ${class}, rather than monkeypatching it")
181     if $meta->is_immutable;
182   for my $attr_name (@_){
183     $meta->remove_attribute($attr_name)
184       if $meta->find_attribute_by_name($attr_name);
185     my $writer = $self->mutator_name_for($attr_name);
186     my @opts = ($meta->has_method($writer) ? () : (writer => $writer) );
187     my $attr = $meta->add_attribute($attr_name, @opts,
188       traits => ['MooseX::Emulate::Class::Accessor::Fast::Meta::Role::Attribute']
189     ) if scalar(@opts);
190     if($writer eq $attr_name && $writer eq $self->accessor_name_for($attr_name)){
191       $meta->add_method("_${attr_name}_accessor" => $attr->get_write_method_ref)
192         unless $meta->has_method("_${attr_name}_accessor");
193     }
194   }
195 }
196
197 =head2 follow_best_practices
198
199 Preface readers with 'get_' and writers with 'set_'.
200 See original L<Class::Accessor> documentation for more information.
201
202 =cut
203
204 sub follow_best_practice{
205   my $self = shift;
206   my $meta = $locate_metaclass->($self);
207
208   $meta->remove_method('mutator_name_for');
209   $meta->remove_method('accessor_name_for');
210   $meta->add_method('mutator_name_for',  sub{ return "set_".$_[1] });
211   $meta->add_method('accessor_name_for', sub{ return "get_".$_[1] });
212 }
213
214 =head2 mutator_name_for
215
216 =head2 accessor_name_for
217
218 See original L<Class::Accessor> documentation for more information.
219
220 =cut
221
222 sub mutator_name_for{  return $_[1] }
223 sub accessor_name_for{ return $_[1] }
224
225 =head2 set
226
227 See original L<Class::Accessor> documentation for more information.
228
229 =cut
230
231 sub set{
232   my $self = shift;
233   my $k = shift;
234   confess "Wrong number of arguments received" unless scalar @_;
235   my $meta = $locate_metaclass->($self);
236
237   confess "No such attribute  '$k'"
238     unless ( my $attr = $meta->find_attribute_by_name($k) );
239   my $writer = $attr->get_write_method;
240   $self->$writer(@_ > 1 ? [@_] : @_);
241 }
242
243 =head2 get
244
245 See original L<Class::Accessor> documentation for more information.
246
247 =cut
248
249 sub get{
250   my $self = shift;
251   confess "Wrong number of arguments received" unless scalar @_;
252   my $meta = $locate_metaclass->($self);
253   my @values;
254
255   for( @_ ){
256     confess "No such attribute  '$_'"
257       unless ( my $attr = $meta->find_attribute_by_name($_) );
258     my $reader = $attr->get_read_method;
259     @_ > 1 ? push(@values, $self->$reader) : return $self->$reader;
260   }
261
262   return @values;
263 }
264
265 sub make_accessor {
266   my($class, $field) = @_;
267   my $meta = $locate_metaclass->($class);
268   my $attr = $meta->find_attribute_by_name($field) || $meta->add_attribute($field,
269       traits => ['MooseX::Emulate::Class::Accessor::Fast::Meta::Role::Attribute']
270   );
271   my $reader = $attr->get_read_method_ref;
272   my $writer = $attr->get_write_method_ref;
273   return sub {
274     my $self = shift;
275     return $reader->($self) unless @_;
276     return $writer->($self,(@_ > 1 ? [@_] : @_));
277   }
278 }
279
280
281 sub make_ro_accessor {
282   my($class, $field) = @_;
283   my $meta = $locate_metaclass->($class);
284   my $attr = $meta->find_attribute_by_name($field) || $meta->add_attribute($field,
285       traits => ['MooseX::Emulate::Class::Accessor::Fast::Meta::Role::Attribute']
286   );
287   return $attr->get_read_method_ref;
288 }
289
290
291 sub make_wo_accessor {
292   my($class, $field) = @_;
293   my $meta = $locate_metaclass->($class);
294   my $attr = $meta->find_attribute_by_name($field) || $meta->add_attribute($field,
295       traits => ['MooseX::Emulate::Class::Accessor::Fast::Meta::Role::Attribute']
296   );
297   return $attr->get_write_method_ref;
298 }
299
300 1;
301
302 =head2 meta
303
304 See L<Moose::Meta::Class>.
305
306 =cut
307
308 =head1 SEE ALSO
309
310 L<Moose>, L<Moose::Meta::Attribute>, L<Class::Accessor>, L<Class::Accessor::Fast>,
311 L<Class::MOP::Attribute>, L<MooseX::Adopt::Class::Accessor::Fast>
312
313 =head1 AUTHORS
314
315 Guillermo Roditi (groditi) E<lt>groditi@cpan.orgE<gt>
316
317 With contributions from:
318
319 =over 4
320
321 =item Tomas Doran E<lt>bobtfish@bobtfish.netE<gt>
322
323 =back
324
325 =head1 LICENSE
326
327 You may distribute this code under the same terms as Perl itself.
328
329 =cut