Commit | Line | Data |
daa0fd7d |
1 | package Moose::Cookbook::Basics::Recipe1; |
471c4f09 |
2 | |
daa0fd7d |
3 | # ABSTRACT: The (always classic) B<Point> example. |
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 |
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. |
cdcae970 |
49 | |
a34602e7 |
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. |
cdcae970 |
53 | |
a34602e7 |
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. |
cdcae970 |
58 | |
a34602e7 |
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. |
cdcae970 |
63 | |
a34602e7 |
64 | Now, onto the keywords. The first one we see here is C<has>, which |
65 | defines an instance attribute in our class: |
cdcae970 |
66 | |
a34602e7 |
67 | has 'x' => (isa => 'Int', is => 'rw', required => 1); |
cdcae970 |
68 | |
a34602e7 |
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. |
cdcae970 |
73 | |
f7d7a976 |
74 | The C<< required => 1 >> parameter means that this attribute must be |
a34602e7 |
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. |
cdcae970 |
77 | |
a34602e7 |
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: |
cdcae970 |
81 | |
82 | sub clear { |
83 | my $self = shift; |
a34602e7 |
84 | $self->x(0); |
85 | $self->y(0); |
cdcae970 |
86 | } |
87 | |
a34602e7 |
88 | That concludes the B<Point> class. |
cdcae970 |
89 | |
a34602e7 |
90 | Next we have a subclass of B<Point>, B<Point3D>. To declare our |
91 | superclass, we use the Moose keyword C<extends>: |
cdcae970 |
92 | |
93 | extends 'Point'; |
94 | |
a34602e7 |
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>. |
cdcae970 |
100 | |
a34602e7 |
101 | It is my opinion that the behavior of C<extends> is more intuitive. |
102 | (2). |
cdcae970 |
103 | |
a34602e7 |
104 | Next 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 |
108 | This attribute is just like B<Point>'s C<x> and C<y> attributes. |
cdcae970 |
109 | |
a34602e7 |
110 | The C<after> keyword demonstrates a Moose feature called "method |
111 | modifiers" (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 |
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). |
cdcae970 |
125 | |
a34602e7 |
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: |
cdcae970 |
129 | |
130 | sub clear { |
131 | my $self = shift; |
132 | $self->SUPER::clear(); |
a34602e7 |
133 | $self->z(0); |
cdcae970 |
134 | } |
135 | |
a34602e7 |
136 | You 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 |
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. |
cdcae970 |
146 | |
a34602e7 |
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 | |
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 |
158 | The C<new> constructor accepts a named argument pair for each |
d68bf053 |
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! |
a34602e7 |
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 |
c79239a2 |
167 | done, you can refer to the |
2c739d1a |
168 | F<t/recipes/moose_cookbook_basics_recipe1.t> test file. |
a34602e7 |
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. |
cdcae970 |
176 | |
a34602e7 |
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>. |
cdcae970 |
184 | |
185 | =head1 CONCLUSION |
186 | |
ec6df2e6 |
187 | This recipe demonstrates some basic Moose concepts, attributes, |
188 | subclassing, and a simple method modifier. |
cdcae970 |
189 | |
190 | =head1 FOOTNOTES |
191 | |
192 | =over 4 |
193 | |
194 | =item (1) |
195 | |
f7d7a976 |
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>. |
cdcae970 |
199 | |
200 | =item (2) |
201 | |
f7d7a976 |
202 | The C<extends> keyword supports multiple inheritance. Simply pass all |
a34602e7 |
203 | of your superclasses to C<extends> as a list: |
204 | |
205 | extends 'Foo', 'Bar', 'Baz'; |
206 | |
207 | =item (3) |
208 | |
1eca36fc |
209 | Moose supports using instance structures other than blessed hash |
f7d7a976 |
210 | references (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 |
220 | The concept of method modifiers is directly ripped off from CLOS. A |
4711f5f7 |
221 | great explanation of them can be found by following this link. |
cdcae970 |
222 | |
223 | L<http://www.gigamonkeys.com/book/object-reorientation-generic-functions.html> |
224 | |
225 | =back |
226 | |
c79239a2 |
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 | |
b10dde3a |
239 | isnt( |
240 | exception { |
241 | $point->y('Foo'); |
242 | }, |
243 | undef, |
244 | '... cannot assign a non-Int to y' |
245 | ); |
c79239a2 |
246 | |
b10dde3a |
247 | isnt( |
248 | exception { |
249 | Point->new(); |
250 | }, |
251 | undef, |
252 | '... must provide required attributes to new' |
253 | ); |
c79239a2 |
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 | |
b10dde3a |
262 | is( |
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 |
270 | isnt( |
271 | exception { |
272 | Point->new( x => 10, y => 'Foo' ); |
273 | }, |
274 | undef, |
275 | '... cannot assign a non-Int to y' |
276 | ); |
c79239a2 |
277 | |
b10dde3a |
278 | isnt( |
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 | |
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 | |
b10dde3a |
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 | ); |
c79239a2 |
310 | |
b10dde3a |
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 | ); |
c79239a2 |
318 | |
b10dde3a |
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 | ); |
c79239a2 |
326 | |
b10dde3a |
327 | isnt( |
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 | |
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 | |
b10dde3a |
343 | isnt( |
344 | Point->meta, Point3D->meta, |
345 | '... they are different metaclasses as well' |
346 | ); |
c79239a2 |
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 | |
a34602e7 |
428 | =cut |