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