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