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