Commit | Line | Data |
471c4f09 |
1 | |
2 | =pod |
3 | |
4 | =head1 NAME |
5 | |
021b8139 |
6 | Moose::Cookbook::Basics::Recipe1 - The (always classic) B<Point> example. |
471c4f09 |
7 | |
8 | =head1 SYNOPSIS |
9 | |
10 | package Point; |
471c4f09 |
11 | use Moose; |
a34602e7 |
12 | |
13 | has 'x' => (isa => 'Int', is => 'rw', required => 1); |
14 | has 'y' => (isa => 'Int', is => 'rw', required => 1); |
15 | |
471c4f09 |
16 | sub clear { |
17 | my $self = shift; |
a34602e7 |
18 | $self->x(0); |
19 | $self->y(0); |
471c4f09 |
20 | } |
a34602e7 |
21 | |
471c4f09 |
22 | package Point3D; |
471c4f09 |
23 | use Moose; |
a34602e7 |
24 | |
471c4f09 |
25 | extends 'Point'; |
a34602e7 |
26 | |
27 | has 'z' => (isa => 'Int', is => 'rw', required => 1); |
28 | |
471c4f09 |
29 | after 'clear' => sub { |
30 | my $self = shift; |
a34602e7 |
31 | $self->z(0); |
471c4f09 |
32 | }; |
33 | |
c79239a2 |
34 | package main; |
d68bf053 |
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 | |
471c4f09 |
42 | =head1 DESCRIPTION |
43 | |
a34602e7 |
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. |
cdcae970 |
47 | |
a34602e7 |
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. |
cdcae970 |
51 | |
a34602e7 |
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. |
cdcae970 |
56 | |
a34602e7 |
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. |
cdcae970 |
61 | |
a34602e7 |
62 | Now, onto the keywords. The first one we see here is C<has>, which |
63 | defines an instance attribute in our class: |
cdcae970 |
64 | |
a34602e7 |
65 | has 'x' => (isa => 'Int', is => 'rw', required => 1); |
cdcae970 |
66 | |
a34602e7 |
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. |
cdcae970 |
71 | |
a34602e7 |
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. |
cdcae970 |
75 | |
a34602e7 |
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: |
cdcae970 |
79 | |
80 | sub clear { |
81 | my $self = shift; |
a34602e7 |
82 | $self->x(0); |
83 | $self->y(0); |
cdcae970 |
84 | } |
85 | |
a34602e7 |
86 | That concludes the B<Point> class. |
cdcae970 |
87 | |
a34602e7 |
88 | Next we have a subclass of B<Point>, B<Point3D>. To declare our |
89 | superclass, we use the Moose keyword C<extends>: |
cdcae970 |
90 | |
91 | extends 'Point'; |
92 | |
a34602e7 |
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>. |
cdcae970 |
98 | |
a34602e7 |
99 | It is my opinion that the behavior of C<extends> is more intuitive. |
100 | (2). |
cdcae970 |
101 | |
a34602e7 |
102 | Next we create a new attribute for B<Point3D> called C<z>. |
cdcae970 |
103 | |
a34602e7 |
104 | has 'z' => (isa => 'Int', is => 'rw', required => 1); |
cdcae970 |
105 | |
a34602e7 |
106 | This attribute is just like B<Point>'s C<x> and C<y> attributes. |
cdcae970 |
107 | |
a34602e7 |
108 | The C<after> keyword demonstrates a Moose feature called "method |
109 | modifiers" (or "advice" for the AOP inclined): |
cdcae970 |
110 | |
111 | after 'clear' => sub { |
112 | my $self = shift; |
a34602e7 |
113 | $self->z(0); |
cdcae970 |
114 | }; |
115 | |
a34602e7 |
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). |
cdcae970 |
123 | |
a34602e7 |
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: |
cdcae970 |
127 | |
128 | sub clear { |
129 | my $self = shift; |
130 | $self->SUPER::clear(); |
a34602e7 |
131 | $self->z(0); |
cdcae970 |
132 | } |
133 | |
a34602e7 |
134 | You could also use another Moose method modifier, C<override>: |
cdcae970 |
135 | |
136 | override 'clear' => sub { |
137 | my $self = shift; |
138 | super(); |
a34602e7 |
139 | $self->z(0); |
cdcae970 |
140 | }; |
cdcae970 |
141 | |
a34602e7 |
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. |
cdcae970 |
144 | |
a34602e7 |
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 | |
d68bf053 |
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); |
cdcae970 |
155 | |
a34602e7 |
156 | The C<new> constructor accepts a named argument pair for each |
d68bf053 |
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! |
a34602e7 |
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 |
c79239a2 |
165 | done, you can refer to the |
166 | F<t/000_recipes/moose_cookbook_basics_recipe1.t> test file. |
a34602e7 |
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. |
cdcae970 |
174 | |
a34602e7 |
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>. |
cdcae970 |
182 | |
183 | =head1 CONCLUSION |
184 | |
ec6df2e6 |
185 | This recipe demonstrates some basic Moose concepts, attributes, |
186 | subclassing, and a simple method modifier. |
cdcae970 |
187 | |
188 | =head1 FOOTNOTES |
189 | |
190 | =over 4 |
191 | |
192 | =item (1) |
193 | |
a34602e7 |
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>. |
cdcae970 |
197 | |
198 | =item (2) |
199 | |
a34602e7 |
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 | |
1eca36fc |
207 | Moose supports using instance structures other than blessed hash |
a34602e7 |
208 | references (such as in a glob reference - see |
209 | L<MooseX::GlobRef::Object>). |
cdcae970 |
210 | |
211 | =back |
212 | |
213 | =head1 SEE ALSO |
214 | |
215 | =over 4 |
216 | |
217 | =item Method Modifiers |
218 | |
d03bd989 |
219 | The concept of method modifiers is directly ripped off from CLOS. A |
4711f5f7 |
220 | great explanation of them can be found by following this link. |
cdcae970 |
221 | |
222 | L<http://www.gigamonkeys.com/book/object-reorientation-generic-functions.html> |
223 | |
224 | =back |
225 | |
8c3d5c88 |
226 | =head1 AUTHORS |
471c4f09 |
227 | |
228 | Stevan Little E<lt>stevan@iinteractive.comE<gt> |
229 | |
8c3d5c88 |
230 | Dave Rolsky E<lt>autarch@urth.orgE<gt> |
231 | |
471c4f09 |
232 | =head1 COPYRIGHT AND LICENSE |
233 | |
7e0492d3 |
234 | Copyright 2006-2010 by Infinity Interactive, Inc. |
471c4f09 |
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 | |
c79239a2 |
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 | |
b10dde3a |
253 | isnt( |
254 | exception { |
255 | $point->y('Foo'); |
256 | }, |
257 | undef, |
258 | '... cannot assign a non-Int to y' |
259 | ); |
c79239a2 |
260 | |
b10dde3a |
261 | isnt( |
262 | exception { |
263 | Point->new(); |
264 | }, |
265 | undef, |
266 | '... must provide required attributes to new' |
267 | ); |
c79239a2 |
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 | |
b10dde3a |
276 | is( |
277 | exception { |
278 | Point->new( x => 0, y => 0 ); |
279 | }, |
280 | undef, |
281 | '... can assign a 0 to x and y' |
282 | ); |
c79239a2 |
283 | |
b10dde3a |
284 | isnt( |
285 | exception { |
286 | Point->new( x => 10, y => 'Foo' ); |
287 | }, |
288 | undef, |
289 | '... cannot assign a non-Int to y' |
290 | ); |
c79239a2 |
291 | |
b10dde3a |
292 | isnt( |
293 | exception { |
294 | Point->new( x => 'Foo', y => 10 ); |
295 | }, |
296 | undef, |
297 | '... cannot assign a non-Int to x' |
298 | ); |
c79239a2 |
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 | |
b10dde3a |
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 | ); |
c79239a2 |
324 | |
b10dde3a |
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 | ); |
c79239a2 |
332 | |
b10dde3a |
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 | ); |
c79239a2 |
340 | |
b10dde3a |
341 | isnt( |
342 | exception { |
343 | Point3D->new( x => 10, y => 3 ); |
344 | }, |
345 | undef, |
346 | '... z is a required attribute for Point3D' |
347 | ); |
c79239a2 |
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 | |
b10dde3a |
357 | isnt( |
358 | Point->meta, Point3D->meta, |
359 | '... they are different metaclasses as well' |
360 | ); |
c79239a2 |
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 | |
a34602e7 |
442 | =cut |