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