1# PODNAME: Moose::Cookbook::Basics::Point_AttributesAndSubclassing
2# ABSTRACT: Point and Point3D classes, showing basic attributes and subclassing.
3
4__END__
5
6=pod
7
8=encoding UTF-8
9
10=head1 NAME
11
12Moose::Cookbook::Basics::Point_AttributesAndSubclassing - Point and Point3D classes, showing basic attributes and subclassing.
13
14=head1 VERSION
15
16version 2.2201
17
18=head1 SYNOPSIS
19
20  package Point;
21  use Moose;
22
23  has 'x' => (isa => 'Int', is => 'rw', required => 1);
24  has 'y' => (isa => 'Int', is => 'rw', required => 1);
25
26  sub clear {
27      my $self = shift;
28      $self->x(0);
29      $self->y(0);
30  }
31
32  package Point3D;
33  use Moose;
34
35  extends 'Point';
36
37  has 'z' => (isa => 'Int', is => 'rw', required => 1);
38
39  after 'clear' => sub {
40      my $self = shift;
41      $self->z(0);
42  };
43
44  package main;
45
46  # hash or hashrefs are ok for the constructor
47  my $point1 = Point->new(x => 5, y => 7);
48  my $point2 = Point->new({x => 5, y => 7});
49
50  my $point3d = Point3D->new(x => 5, y => 42, z => -5);
51
52=head1 DESCRIPTION
53
54This is the classic Point example. It is taken directly from the Perl
556 Apocalypse 12 document, and is similar to the example found in the
56classic K&R C book as well.
57
58As with all Perl 5 classes, a Moose class is defined in a package.
59Moose handles turning on C<strict> and C<warnings> for us, so all we
60need to do is say C<use Moose>, and no kittens will die.
61
62When Moose is loaded, it exports a set of sugar functions into our
63package. This means that we import some functions which serve as Moose
64"keywords". These aren't real language keywords, they're just Perl
65functions exported into our package.
66
67Moose automatically makes our package a subclass of L<Moose::Object>.
68The L<Moose::Object> class provides us with a constructor that
69respects our attributes, as well other features. See L<Moose::Object>
70for details.
71
72Now, onto the keywords. The first one we see here is C<has>, which
73defines an instance attribute in our class:
74
75  has 'x' => (isa => 'Int', is => 'rw', required => 1);
76
77This will create an attribute named C<x>. The C<isa> parameter says
78that we expect the value stored in this attribute to pass the type
79constraint for C<Int> (1). The accessor generated for this attribute
80will be read-write.
81
82The C<< required => 1 >> parameter means that this attribute must be
83provided when a new object is created. A point object without
84coordinates doesn't make much sense, so we don't allow it.
85
86We have defined our attributes; next we define our methods. In Moose,
87as with regular Perl 5 OO, a method is just a subroutine defined
88within the package:
89
90  sub clear {
91      my $self = shift;
92      $self->x(0);
93      $self->y(0);
94  }
95
96That concludes the B<Point> class.
97
98Next we have a subclass of B<Point>, B<Point3D>. To declare our
99superclass, we use the Moose keyword C<extends>:
100
101  extends 'Point';
102
103The C<extends> keyword works much like C<use base>/C<use parent>. First,
104it will attempt to load your class if needed. However, unlike C<base>, the
105C<extends> keyword will I<overwrite> any previous values in your
106package's C<@ISA>, where C<use base> will C<push> values onto the
107package's C<@ISA>.
108
109It is my opinion that the behavior of C<extends> is more intuitive.
110(2).
111
112Next we create a new attribute for B<Point3D> called C<z>.
113
114  has 'z' => (isa => 'Int', is => 'rw', required => 1);
115
116This attribute is just like B<Point>'s C<x> and C<y> attributes.
117
118The C<after> keyword demonstrates a Moose feature called "method
119modifiers" (or "advice" for the AOP inclined):
120
121  after 'clear' => sub {
122      my $self = shift;
123      $self->z(0);
124  };
125
126When C<clear> is called on a B<Point3D> object, our modifier method
127gets called as well. Unsurprisingly, the modifier is called I<after>
128the real method.
129
130In this case, the real C<clear> method is inherited from B<Point>. Our
131modifier method receives the same arguments as those passed to the
132modified method (just C<$self> here).
133
134Of course, using the C<after> modifier is not the only way to
135accomplish this. This B<is> Perl, right? You can get the same results
136with this code:
137
138  sub clear {
139      my $self = shift;
140      $self->SUPER::clear();
141      $self->z(0);
142  }
143
144You could also use another Moose method modifier, C<override>:
145
146  override 'clear' => sub {
147      my $self = shift;
148      super();
149      $self->z(0);
150  };
151
152The C<override> modifier allows you to use the C<super> keyword to
153dispatch to the superclass's method in a very Ruby-ish style.
154
155The choice of whether to use a method modifier, and which one to use,
156is often a question of style as much as functionality.
157
158Since B<Point> inherits from L<Moose::Object>, it will also inherit
159the default L<Moose::Object> constructor:
160
161  my $point1 = Point->new(x => 5, y => 7);
162  my $point2 = Point->new({x => 5, y => 7});
163
164  my $point3d = Point3D->new(x => 5, y => 42, z => -5);
165
166The C<new> constructor accepts a named argument pair for each
167attribute defined by the class, which you can provide as a hash or
168hash reference. In this particular example, the attributes are
169required, and calling C<new> without them will throw an error.
170
171  my $point = Point->new( x => 5 ); # no y, kaboom!
172
173From here on, we can use C<$point> and C<$point3d> just as you would
174any other Perl 5 object. For a more detailed example of what can be
175done, you can refer to the
176F<t/recipes/basics_point_attributesandsubclassing.t> test file.
177
178=head2 Moose Objects are Just Hashrefs
179
180While this all may appear rather magical, it's important to realize
181that Moose objects are just hash references under the hood (3). For
182example, you could pass C<$self> to C<Data::Dumper> and you'd get
183exactly what you'd expect.
184
185You could even poke around inside the object's data structure, but
186that is strongly discouraged.
187
188The fact that Moose objects are hashrefs means it is easy to use Moose
189to extend non-Moose classes, as long as they too are hash
190references. If you want to extend a non-hashref class, check out
191C<MooseX::InsideOut>.
192
193=head1 CONCLUSION
194
195This recipe demonstrates some basic Moose concepts, attributes,
196subclassing, and a simple method modifier.
197
198=head1 FOOTNOTES
199
200=over 4
201
202=item (1)
203
204Moose provides a number of builtin type constraints, of which C<Int>
205is one. For more information on the type constraint system, see
206L<Moose::Util::TypeConstraints>.
207
208=item (2)
209
210The C<extends> keyword supports multiple inheritance. Simply pass all
211of your superclasses to C<extends> as a list:
212
213  extends 'Foo', 'Bar', 'Baz';
214
215=item (3)
216
217Moose supports using instance structures other than blessed hash
218references (such as glob references - see L<MooseX::GlobRef>).
219
220=back
221
222=head1 SEE ALSO
223
224=over 4
225
226=item Method Modifiers
227
228The concept of method modifiers is directly ripped off from CLOS. A
229great explanation of them can be found by following this link.
230
231L<http://www.gigamonkeys.com/book/object-reorientation-generic-functions.html>
232
233=back
234
235=begin testing
236
237my $point = Point->new( x => 1, y => 2 );
238isa_ok( $point, 'Point' );
239isa_ok( $point, 'Moose::Object' );
240
241is( $point->x, 1, '... got the right value for x' );
242is( $point->y, 2, '... got the right value for y' );
243
244$point->y(10);
245is( $point->y, 10, '... got the right (changed) value for y' );
246
247isnt(
248    exception {
249        $point->y('Foo');
250    },
251    undef,
252    '... cannot assign a non-Int to y'
253);
254
255isnt(
256    exception {
257        Point->new();
258    },
259    undef,
260    '... must provide required attributes to new'
261);
262
263$point->clear();
264
265is( $point->x, 0, '... got the right (cleared) value for x' );
266is( $point->y, 0, '... got the right (cleared) value for y' );
267
268# check the type constraints on the constructor
269
270is(
271    exception {
272        Point->new( x => 0, y => 0 );
273    },
274    undef,
275    '... can assign a 0 to x and y'
276);
277
278isnt(
279    exception {
280        Point->new( x => 10, y => 'Foo' );
281    },
282    undef,
283    '... cannot assign a non-Int to y'
284);
285
286isnt(
287    exception {
288        Point->new( x => 'Foo', y => 10 );
289    },
290    undef,
291    '... cannot assign a non-Int to x'
292);
293
294# Point3D
295
296my $point3d = Point3D->new( { x => 10, y => 15, z => 3 } );
297isa_ok( $point3d, 'Point3D' );
298isa_ok( $point3d, 'Point' );
299isa_ok( $point3d, 'Moose::Object' );
300
301is( $point3d->x,     10, '... got the right value for x' );
302is( $point3d->y,     15, '... got the right value for y' );
303is( $point3d->{'z'}, 3,  '... got the right value for z' );
304
305$point3d->clear();
306
307is( $point3d->x, 0, '... got the right (cleared) value for x' );
308is( $point3d->y, 0, '... got the right (cleared) value for y' );
309is( $point3d->z, 0, '... got the right (cleared) value for z' );
310
311isnt(
312    exception {
313        Point3D->new( x => 10, y => 'Foo', z => 3 );
314    },
315    undef,
316    '... cannot assign a non-Int to y'
317);
318
319isnt(
320    exception {
321        Point3D->new( x => 'Foo', y => 10, z => 3 );
322    },
323    undef,
324    '... cannot assign a non-Int to x'
325);
326
327isnt(
328    exception {
329        Point3D->new( x => 0, y => 10, z => 'Bar' );
330    },
331    undef,
332    '... cannot assign a non-Int to z'
333);
334
335isnt(
336    exception {
337        Point3D->new( x => 10, y => 3 );
338    },
339    undef,
340    '... z is a required attribute for Point3D'
341);
342
343# test some class introspection
344
345can_ok( 'Point', 'meta' );
346isa_ok( Point->meta, 'Moose::Meta::Class' );
347
348can_ok( 'Point3D', 'meta' );
349isa_ok( Point3D->meta, 'Moose::Meta::Class' );
350
351isnt(
352    Point->meta, Point3D->meta,
353    '... they are different metaclasses as well'
354);
355
356# poke at Point
357
358is_deeply(
359    [ Point->meta->superclasses ],
360    ['Moose::Object'],
361    '... Point got the automagic base class'
362);
363
364my @Point_methods = qw(meta x y clear);
365my @Point_attrs = ( 'x', 'y' );
366
367is_deeply(
368    [ sort @Point_methods ],
369    [ sort Point->meta->get_method_list() ],
370    '... we match the method list for Point'
371);
372
373is_deeply(
374    [ sort @Point_attrs ],
375    [ sort Point->meta->get_attribute_list() ],
376    '... we match the attribute list for Point'
377);
378
379foreach my $method (@Point_methods) {
380    ok( Point->meta->has_method($method),
381        '... Point has the method "' . $method . '"' );
382}
383
384foreach my $attr_name (@Point_attrs) {
385    ok( Point->meta->has_attribute($attr_name),
386        '... Point has the attribute "' . $attr_name . '"' );
387    my $attr = Point->meta->get_attribute($attr_name);
388    ok( $attr->has_type_constraint,
389        '... Attribute ' . $attr_name . ' has a type constraint' );
390    isa_ok( $attr->type_constraint, 'Moose::Meta::TypeConstraint' );
391    is( $attr->type_constraint->name, 'Int',
392        '... Attribute ' . $attr_name . ' has an Int type constraint' );
393}
394
395# poke at Point3D
396
397is_deeply(
398    [ Point3D->meta->superclasses ],
399    ['Point'],
400    '... Point3D gets the parent given to it'
401);
402
403my @Point3D_methods = qw( meta z clear );
404my @Point3D_attrs   = ('z');
405
406is_deeply(
407    [ sort @Point3D_methods ],
408    [ sort Point3D->meta->get_method_list() ],
409    '... we match the method list for Point3D'
410);
411
412is_deeply(
413    [ sort @Point3D_attrs ],
414    [ sort Point3D->meta->get_attribute_list() ],
415    '... we match the attribute list for Point3D'
416);
417
418foreach my $method (@Point3D_methods) {
419    ok( Point3D->meta->has_method($method),
420        '... Point3D has the method "' . $method . '"' );
421}
422
423foreach my $attr_name (@Point3D_attrs) {
424    ok( Point3D->meta->has_attribute($attr_name),
425        '... Point3D has the attribute "' . $attr_name . '"' );
426    my $attr = Point3D->meta->get_attribute($attr_name);
427    ok( $attr->has_type_constraint,
428        '... Attribute ' . $attr_name . ' has a type constraint' );
429    isa_ok( $attr->type_constraint, 'Moose::Meta::TypeConstraint' );
430    is( $attr->type_constraint->name, 'Int',
431        '... Attribute ' . $attr_name . ' has an Int type constraint' );
432}
433
434=end testing
435
436=head1 AUTHORS
437
438=over 4
439
440=item *
441
442Stevan Little <stevan@cpan.org>
443
444=item *
445
446Dave Rolsky <autarch@urth.org>
447
448=item *
449
450Jesse Luehrs <doy@cpan.org>
451
452=item *
453
454Shawn M Moore <sartak@cpan.org>
455
456=item *
457
458יובל קוג'מן (Yuval Kogman) <nothingmuch@woobling.org>
459
460=item *
461
462Karen Etheridge <ether@cpan.org>
463
464=item *
465
466Florian Ragwitz <rafl@debian.org>
467
468=item *
469
470Hans Dieter Pearcey <hdp@cpan.org>
471
472=item *
473
474Chris Prather <chris@prather.org>
475
476=item *
477
478Matt S Trout <mstrout@cpan.org>
479
480=back
481
482=head1 COPYRIGHT AND LICENSE
483
484This software is copyright (c) 2006 by Infinity Interactive, Inc.
485
486This is free software; you can redistribute it and/or modify it under
487the same terms as the Perl 5 programming language system itself.
488
489=cut
490