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 | |
a34602e7 |
74 | The C<< requires => 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. |
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 | |
a34602e7 |
196 | Moose provides a number of builtin type constraints are provided by, |
197 | of which C<Int> is one. For more information on the type constraint |
198 | system, see L<Moose::Util::TypeConstraints>. |
cdcae970 |
199 | |
200 | =item (2) |
201 | |
a34602e7 |
202 | The C<extends> keyword support 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 | |
1eca36fc |
209 | Moose supports using instance structures other than blessed hash |
a34602e7 |
210 | references (such as in a glob reference - see |
211 | L<MooseX::GlobRef::Object>). |
cdcae970 |
212 | |
213 | =back |
214 | |
215 | =head1 SEE ALSO |
216 | |
217 | =over 4 |
218 | |
219 | =item Method Modifiers |
220 | |
d03bd989 |
221 | The concept of method modifiers is directly ripped off from CLOS. A |
4711f5f7 |
222 | great explanation of them can be found by following this link. |
cdcae970 |
223 | |
224 | L<http://www.gigamonkeys.com/book/object-reorientation-generic-functions.html> |
225 | |
226 | =back |
227 | |
c79239a2 |
228 | =begin testing |
229 | |
230 | my $point = Point->new( x => 1, y => 2 ); |
231 | isa_ok( $point, 'Point' ); |
232 | isa_ok( $point, 'Moose::Object' ); |
233 | |
234 | is( $point->x, 1, '... got the right value for x' ); |
235 | is( $point->y, 2, '... got the right value for y' ); |
236 | |
237 | $point->y(10); |
238 | is( $point->y, 10, '... got the right (changed) value for y' ); |
239 | |
b10dde3a |
240 | isnt( |
241 | exception { |
242 | $point->y('Foo'); |
243 | }, |
244 | undef, |
245 | '... cannot assign a non-Int to y' |
246 | ); |
c79239a2 |
247 | |
b10dde3a |
248 | isnt( |
249 | exception { |
250 | Point->new(); |
251 | }, |
252 | undef, |
253 | '... must provide required attributes to new' |
254 | ); |
c79239a2 |
255 | |
256 | $point->clear(); |
257 | |
258 | is( $point->x, 0, '... got the right (cleared) value for x' ); |
259 | is( $point->y, 0, '... got the right (cleared) value for y' ); |
260 | |
261 | # check the type constraints on the constructor |
262 | |
b10dde3a |
263 | is( |
264 | exception { |
265 | Point->new( x => 0, y => 0 ); |
266 | }, |
267 | undef, |
268 | '... can assign a 0 to x and y' |
269 | ); |
c79239a2 |
270 | |
b10dde3a |
271 | isnt( |
272 | exception { |
273 | Point->new( x => 10, y => 'Foo' ); |
274 | }, |
275 | undef, |
276 | '... cannot assign a non-Int to y' |
277 | ); |
c79239a2 |
278 | |
b10dde3a |
279 | isnt( |
280 | exception { |
281 | Point->new( x => 'Foo', y => 10 ); |
282 | }, |
283 | undef, |
284 | '... cannot assign a non-Int to x' |
285 | ); |
c79239a2 |
286 | |
287 | # Point3D |
288 | |
289 | my $point3d = Point3D->new( { x => 10, y => 15, z => 3 } ); |
290 | isa_ok( $point3d, 'Point3D' ); |
291 | isa_ok( $point3d, 'Point' ); |
292 | isa_ok( $point3d, 'Moose::Object' ); |
293 | |
294 | is( $point3d->x, 10, '... got the right value for x' ); |
295 | is( $point3d->y, 15, '... got the right value for y' ); |
296 | is( $point3d->{'z'}, 3, '... got the right value for z' ); |
297 | |
298 | $point3d->clear(); |
299 | |
300 | is( $point3d->x, 0, '... got the right (cleared) value for x' ); |
301 | is( $point3d->y, 0, '... got the right (cleared) value for y' ); |
302 | is( $point3d->z, 0, '... got the right (cleared) value for z' ); |
303 | |
b10dde3a |
304 | isnt( |
305 | exception { |
306 | Point3D->new( x => 10, y => 'Foo', z => 3 ); |
307 | }, |
308 | undef, |
309 | '... cannot assign a non-Int to y' |
310 | ); |
c79239a2 |
311 | |
b10dde3a |
312 | isnt( |
313 | exception { |
314 | Point3D->new( x => 'Foo', y => 10, z => 3 ); |
315 | }, |
316 | undef, |
317 | '... cannot assign a non-Int to x' |
318 | ); |
c79239a2 |
319 | |
b10dde3a |
320 | isnt( |
321 | exception { |
322 | Point3D->new( x => 0, y => 10, z => 'Bar' ); |
323 | }, |
324 | undef, |
325 | '... cannot assign a non-Int to z' |
326 | ); |
c79239a2 |
327 | |
b10dde3a |
328 | isnt( |
329 | exception { |
330 | Point3D->new( x => 10, y => 3 ); |
331 | }, |
332 | undef, |
333 | '... z is a required attribute for Point3D' |
334 | ); |
c79239a2 |
335 | |
336 | # test some class introspection |
337 | |
338 | can_ok( 'Point', 'meta' ); |
339 | isa_ok( Point->meta, 'Moose::Meta::Class' ); |
340 | |
341 | can_ok( 'Point3D', 'meta' ); |
342 | isa_ok( Point3D->meta, 'Moose::Meta::Class' ); |
343 | |
b10dde3a |
344 | isnt( |
345 | Point->meta, Point3D->meta, |
346 | '... they are different metaclasses as well' |
347 | ); |
c79239a2 |
348 | |
349 | # poke at Point |
350 | |
351 | is_deeply( |
352 | [ Point->meta->superclasses ], |
353 | ['Moose::Object'], |
354 | '... Point got the automagic base class' |
355 | ); |
356 | |
357 | my @Point_methods = qw(meta x y clear); |
358 | my @Point_attrs = ( 'x', 'y' ); |
359 | |
360 | is_deeply( |
361 | [ sort @Point_methods ], |
362 | [ sort Point->meta->get_method_list() ], |
363 | '... we match the method list for Point' |
364 | ); |
365 | |
366 | is_deeply( |
367 | [ sort @Point_attrs ], |
368 | [ sort Point->meta->get_attribute_list() ], |
369 | '... we match the attribute list for Point' |
370 | ); |
371 | |
372 | foreach my $method (@Point_methods) { |
373 | ok( Point->meta->has_method($method), |
374 | '... Point has the method "' . $method . '"' ); |
375 | } |
376 | |
377 | foreach my $attr_name (@Point_attrs) { |
378 | ok( Point->meta->has_attribute($attr_name), |
379 | '... Point has the attribute "' . $attr_name . '"' ); |
380 | my $attr = Point->meta->get_attribute($attr_name); |
381 | ok( $attr->has_type_constraint, |
382 | '... Attribute ' . $attr_name . ' has a type constraint' ); |
383 | isa_ok( $attr->type_constraint, 'Moose::Meta::TypeConstraint' ); |
384 | is( $attr->type_constraint->name, 'Int', |
385 | '... Attribute ' . $attr_name . ' has an Int type constraint' ); |
386 | } |
387 | |
388 | # poke at Point3D |
389 | |
390 | is_deeply( |
391 | [ Point3D->meta->superclasses ], |
392 | ['Point'], |
393 | '... Point3D gets the parent given to it' |
394 | ); |
395 | |
396 | my @Point3D_methods = qw( meta z clear ); |
397 | my @Point3D_attrs = ('z'); |
398 | |
399 | is_deeply( |
400 | [ sort @Point3D_methods ], |
401 | [ sort Point3D->meta->get_method_list() ], |
402 | '... we match the method list for Point3D' |
403 | ); |
404 | |
405 | is_deeply( |
406 | [ sort @Point3D_attrs ], |
407 | [ sort Point3D->meta->get_attribute_list() ], |
408 | '... we match the attribute list for Point3D' |
409 | ); |
410 | |
411 | foreach my $method (@Point3D_methods) { |
412 | ok( Point3D->meta->has_method($method), |
413 | '... Point3D has the method "' . $method . '"' ); |
414 | } |
415 | |
416 | foreach my $attr_name (@Point3D_attrs) { |
417 | ok( Point3D->meta->has_attribute($attr_name), |
418 | '... Point3D has the attribute "' . $attr_name . '"' ); |
419 | my $attr = Point3D->meta->get_attribute($attr_name); |
420 | ok( $attr->has_type_constraint, |
421 | '... Attribute ' . $attr_name . ' has a type constraint' ); |
422 | isa_ok( $attr->type_constraint, 'Moose::Meta::TypeConstraint' ); |
423 | is( $attr->type_constraint->name, 'Int', |
424 | '... Attribute ' . $attr_name . ' has an Int type constraint' ); |
425 | } |
426 | |
427 | =end testing |
428 | |
a34602e7 |
429 | =cut |