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 | |
2840a3b2 |
234 | Copyright 2006-2009 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 | |
253 | dies_ok { |
254 | $point->y('Foo'); |
255 | } |
256 | '... cannot assign a non-Int to y'; |
257 | |
258 | dies_ok { |
259 | Point->new(); |
260 | } |
261 | '... must provide required attributes to new'; |
262 | |
263 | $point->clear(); |
264 | |
265 | is( $point->x, 0, '... got the right (cleared) value for x' ); |
266 | is( $point->y, 0, '... got the right (cleared) value for y' ); |
267 | |
268 | # check the type constraints on the constructor |
269 | |
270 | lives_ok { |
271 | Point->new( x => 0, y => 0 ); |
272 | } |
273 | '... can assign a 0 to x and y'; |
274 | |
275 | dies_ok { |
276 | Point->new( x => 10, y => 'Foo' ); |
277 | } |
278 | '... cannot assign a non-Int to y'; |
279 | |
280 | dies_ok { |
281 | Point->new( x => 'Foo', y => 10 ); |
282 | } |
283 | '... cannot assign a non-Int to x'; |
284 | |
285 | # Point3D |
286 | |
287 | my $point3d = Point3D->new( { x => 10, y => 15, z => 3 } ); |
288 | isa_ok( $point3d, 'Point3D' ); |
289 | isa_ok( $point3d, 'Point' ); |
290 | isa_ok( $point3d, 'Moose::Object' ); |
291 | |
292 | is( $point3d->x, 10, '... got the right value for x' ); |
293 | is( $point3d->y, 15, '... got the right value for y' ); |
294 | is( $point3d->{'z'}, 3, '... got the right value for z' ); |
295 | |
296 | $point3d->clear(); |
297 | |
298 | is( $point3d->x, 0, '... got the right (cleared) value for x' ); |
299 | is( $point3d->y, 0, '... got the right (cleared) value for y' ); |
300 | is( $point3d->z, 0, '... got the right (cleared) value for z' ); |
301 | |
302 | dies_ok { |
303 | Point3D->new( x => 10, y => 'Foo', z => 3 ); |
304 | } |
305 | '... cannot assign a non-Int to y'; |
306 | |
307 | dies_ok { |
308 | Point3D->new( x => 'Foo', y => 10, z => 3 ); |
309 | } |
310 | '... cannot assign a non-Int to x'; |
311 | |
312 | dies_ok { |
313 | Point3D->new( x => 0, y => 10, z => 'Bar' ); |
314 | } |
315 | '... cannot assign a non-Int to z'; |
316 | |
317 | dies_ok { |
318 | Point3D->new( x => 10, y => 3 ); |
319 | } |
320 | '... z is a required attribute for Point3D'; |
321 | |
322 | # test some class introspection |
323 | |
324 | can_ok( 'Point', 'meta' ); |
325 | isa_ok( Point->meta, 'Moose::Meta::Class' ); |
326 | |
327 | can_ok( 'Point3D', 'meta' ); |
328 | isa_ok( Point3D->meta, 'Moose::Meta::Class' ); |
329 | |
330 | isnt( Point->meta, Point3D->meta, |
331 | '... they are different metaclasses as well' ); |
332 | |
333 | # poke at Point |
334 | |
335 | is_deeply( |
336 | [ Point->meta->superclasses ], |
337 | ['Moose::Object'], |
338 | '... Point got the automagic base class' |
339 | ); |
340 | |
341 | my @Point_methods = qw(meta x y clear); |
342 | my @Point_attrs = ( 'x', 'y' ); |
343 | |
344 | is_deeply( |
345 | [ sort @Point_methods ], |
346 | [ sort Point->meta->get_method_list() ], |
347 | '... we match the method list for Point' |
348 | ); |
349 | |
350 | is_deeply( |
351 | [ sort @Point_attrs ], |
352 | [ sort Point->meta->get_attribute_list() ], |
353 | '... we match the attribute list for Point' |
354 | ); |
355 | |
356 | foreach my $method (@Point_methods) { |
357 | ok( Point->meta->has_method($method), |
358 | '... Point has the method "' . $method . '"' ); |
359 | } |
360 | |
361 | foreach my $attr_name (@Point_attrs) { |
362 | ok( Point->meta->has_attribute($attr_name), |
363 | '... Point has the attribute "' . $attr_name . '"' ); |
364 | my $attr = Point->meta->get_attribute($attr_name); |
365 | ok( $attr->has_type_constraint, |
366 | '... Attribute ' . $attr_name . ' has a type constraint' ); |
367 | isa_ok( $attr->type_constraint, 'Moose::Meta::TypeConstraint' ); |
368 | is( $attr->type_constraint->name, 'Int', |
369 | '... Attribute ' . $attr_name . ' has an Int type constraint' ); |
370 | } |
371 | |
372 | # poke at Point3D |
373 | |
374 | is_deeply( |
375 | [ Point3D->meta->superclasses ], |
376 | ['Point'], |
377 | '... Point3D gets the parent given to it' |
378 | ); |
379 | |
380 | my @Point3D_methods = qw( meta z clear ); |
381 | my @Point3D_attrs = ('z'); |
382 | |
383 | is_deeply( |
384 | [ sort @Point3D_methods ], |
385 | [ sort Point3D->meta->get_method_list() ], |
386 | '... we match the method list for Point3D' |
387 | ); |
388 | |
389 | is_deeply( |
390 | [ sort @Point3D_attrs ], |
391 | [ sort Point3D->meta->get_attribute_list() ], |
392 | '... we match the attribute list for Point3D' |
393 | ); |
394 | |
395 | foreach my $method (@Point3D_methods) { |
396 | ok( Point3D->meta->has_method($method), |
397 | '... Point3D has the method "' . $method . '"' ); |
398 | } |
399 | |
400 | foreach my $attr_name (@Point3D_attrs) { |
401 | ok( Point3D->meta->has_attribute($attr_name), |
402 | '... Point3D has the attribute "' . $attr_name . '"' ); |
403 | my $attr = Point3D->meta->get_attribute($attr_name); |
404 | ok( $attr->has_type_constraint, |
405 | '... Attribute ' . $attr_name . ' has a type constraint' ); |
406 | isa_ok( $attr->type_constraint, 'Moose::Meta::TypeConstraint' ); |
407 | is( $attr->type_constraint->name, 'Int', |
408 | '... Attribute ' . $attr_name . ' has an Int type constraint' ); |
409 | } |
410 | |
411 | =end testing |
412 | |
a34602e7 |
413 | =cut |