note how BUILD works here, since it's the first time it's mentioned
[gitmo/Moose.git] / lib / Moose / Cookbook / Basics / Recipe4.pod
CommitLineData
daa0fd7d 1package Moose::Cookbook::Basics::Recipe4;
2
3# ABSTRACT: Subtypes, and modeling a simple B<Company> class hierarchy
4
5__END__
6
471c4f09 7
8=pod
9
5547fba7 10=begin testing-SETUP
c79239a2 11
0adca353 12use Test::Requires {
13 'Locale::US' => '0',
14 'Regexp::Common' => '0',
15};
c79239a2 16
5547fba7 17=end testing-SETUP
c79239a2 18
471c4f09 19=head1 SYNOPSIS
36c99105 20
471c4f09 21 package Address;
471c4f09 22 use Moose;
05d9eaf6 23 use Moose::Util::TypeConstraints;
36c99105 24
471c4f09 25 use Locale::US;
26 use Regexp::Common 'zip';
36c99105 27
471c4f09 28 my $STATES = Locale::US->new;
0b3811a6 29 subtype 'USState'
471c4f09 30 => as Str
31 => where {
36c99105 32 ( exists $STATES->{code2state}{ uc($_) }
33 || exists $STATES->{state2code}{ uc($_) } );
34 };
35
0b3811a6 36 subtype 'USZipCode'
471c4f09 37 => as Value
38 => where {
36c99105 39 /^$RE{zip}{US}{-extended => 'allow'}$/;
40 };
41
42 has 'street' => ( is => 'rw', isa => 'Str' );
43 has 'city' => ( is => 'rw', isa => 'Str' );
44 has 'state' => ( is => 'rw', isa => 'USState' );
45 has 'zip_code' => ( is => 'rw', isa => 'USZipCode' );
46
f1917f58 47 package Company;
48 use Moose;
49 use Moose::Util::TypeConstraints;
36c99105 50
51 has 'name' => ( is => 'rw', isa => 'Str', required => 1 );
52 has 'address' => ( is => 'rw', isa => 'Address' );
53 has 'employees' => ( is => 'rw', isa => 'ArrayRef[Employee]' );
54
f1917f58 55 sub BUILD {
36c99105 56 my ( $self, $params ) = @_;
922a97e9 57 foreach my $employee ( @{ $self->employees || [] } ) {
58 $employee->employer($self);
f1917f58 59 }
60 }
36c99105 61
f1917f58 62 after 'employees' => sub {
36c99105 63 my ( $self, $employees ) = @_;
922a97e9 64 foreach my $employee ( @{ $employees || [] } ) {
65 $employee->employer($self);
f1917f58 66 }
36c99105 67 };
68
471c4f09 69 package Person;
471c4f09 70 use Moose;
36c99105 71
72 has 'first_name' => ( is => 'rw', isa => 'Str', required => 1 );
73 has 'last_name' => ( is => 'rw', isa => 'Str', required => 1 );
74 has 'middle_initial' => (
75 is => 'rw', isa => 'Str',
76 predicate => 'has_middle_initial'
77 );
78 has 'address' => ( is => 'rw', isa => 'Address' );
79
471c4f09 80 sub full_name {
81 my $self = shift;
36c99105 82 return $self->first_name
83 . (
84 $self->has_middle_initial
85 ? ' ' . $self->middle_initial . '. '
86 : ' '
87 ) . $self->last_name;
471c4f09 88 }
36c99105 89
471c4f09 90 package Employee;
36c99105 91 use Moose;
92
471c4f09 93 extends 'Person';
36c99105 94
4a6b74bd 95 has 'title' => ( is => 'rw', isa => 'Str', required => 1 );
96 has 'employer' => ( is => 'rw', isa => 'Company', weak_ref => 1 );
36c99105 97
471c4f09 98 override 'full_name' => sub {
99 my $self = shift;
36c99105 100 super() . ', ' . $self->title;
471c4f09 101 };
2f04a0fc 102
471c4f09 103=head1 DESCRIPTION
104
4a6b74bd 105This recipe introduces the C<subtype> sugar function from
106L<Moose::Util::TypeConstraints>. The C<subtype> function lets you
107declaratively create type constraints without building an entire
108class.
172e0738 109
4a6b74bd 110In the recipe we also make use of L<Locale::US> and L<Regexp::Common>
cad0dd79 111to build constraints, showing how constraints can make use of existing
112CPAN tools for data validation.
36c99105 113
16fb3624 114Finally, we introduce the C<required> attribute option.
4a6b74bd 115
21ec1978 116In the C<Address> class we define two subtypes. The first uses the
19320607 117L<Locale::US> module to check the validity of a state. It accepts
4a6b74bd 118either a state abbreviation of full name.
119
120A state will be passed in as a string, so we make our C<USState> type
121a subtype of Moose's builtin C<Str> type. This is done using the C<as>
122sugar. The actual constraint is defined using C<where>. This function
123accepts a single subroutine reference. That subroutine will be called
124with the value to be checked in C<$_> (1). It is expected to return a
125true or false value indicating whether the value is valid for the
126type.
172e0738 127
4a6b74bd 128We can now use the C<USState> type just like Moose's builtin types:
172e0738 129
36c99105 130 has 'state' => ( is => 'rw', isa => 'USState' );
172e0738 131
4a6b74bd 132When the C<state> attribute is set, the value is checked against the
133C<USState> constraint. If the value is not valid, an exception will be
134thrown.
172e0738 135
4a6b74bd 136The next C<subtype>, C<USZipCode>, uses
137L<Regexp::Common>. L<Regexp::Common> includes a regex for validating
138US zip codes. We use this constraint for the C<zip_code> attribute.
172e0738 139
0b3811a6 140 subtype 'USZipCode'
172e0738 141 => as Value
142 => where {
36c99105 143 /^$RE{zip}{US}{-extended => 'allow'}$/;
144 };
172e0738 145
4a6b74bd 146Using a subtype instead of requiring a class for each type greatly
147simplifies the code. We don't really need a class for these types, as
148they're just strings, but we do want to ensure that they're valid.
149
150The type constraints we created are reusable. Type constraints are
9ec4a49a 151stored by name in a global registry, which means that we can refer to
4a6b74bd 152them in other classes. Because the registry is global, we do recommend
9ec4a49a 153that you use some sort of namespacing in real applications,
154like C<MyApp::Type::USState> (just as you would do with class names).
4a6b74bd 155
156These two subtypes allow us to define a simple C<Address> class.
172e0738 157
4a6b74bd 158Then we define our C<Company> class, which has an address. As we saw
159in earlier recipes, Moose automatically creates a type constraint for
160each our classes, so we can use that for the C<Company> class's
161C<address> attribute:
172e0738 162
36c99105 163 has 'address' => ( is => 'rw', isa => 'Address' );
172e0738 164
4a6b74bd 165A company also needs a name:
172e0738 166
36c99105 167 has 'name' => ( is => 'rw', isa => 'Str', required => 1 );
172e0738 168
16fb3624 169This introduces a new attribute option, C<required>. If an attribute
170is required, then it must be passed to the class's constructor, or an
171exception will be thrown. It's important to understand that a
172C<required> attribute can still be false or C<undef>, if its type
173constraint allows that.
f1917f58 174
4a6b74bd 175The next attribute, C<employees>, uses a I<parameterized> type
176constraint:
36c99105 177
178 has 'employees' => ( is => 'rw', isa => 'ArrayRef[Employee]' );
07cde929 179
4a6b74bd 180This constraint says that C<employees> must be an array reference
181where each element of the array is an C<Employee> object. It's worth
182noting that an I<empty> array reference also satisfies this
183constraint.
184
cad0dd79 185Parameterizable type constraints (or "container types"), such as
4a6b74bd 186C<ArrayRef[`a]>, can be made more specific with a type parameter. In
187fact, we can arbitrarily nest these types, producing something like
188C<HashRef[ArrayRef[Int]]>. However, you can also just use the type by
189itself, so C<ArrayRef> is legal. (2)
190
191If you jump down to the definition of the C<Employee> class, you will
192see that it has an C<employer> attribute.
193
194When we set the C<employees> for a C<Company> we want to make sure
195that each of these employee objects refers back to the right
196C<Company> in its C<employer> attribute.
197
198To do that, we need to hook into object construction. Moose lets us do
199this by writing a C<BUILD> method in our class. When your class
172b4ff6 200defines a C<BUILD> method, it will be called immediately after
4a6b74bd 201object construction, but before the object is returned to the caller
2859ba08 202(3). Note that all C<BUILD> methods in your class hierarchy will be
203called automatically; there is no need to (and you should not) call
204the superclass C<BUILD> method.
4a6b74bd 205
206The C<Company> class uses the C<BUILD> method to ensure that each
207employee of a company has the proper C<Company> object in its
208C<employer> attribute:
36c99105 209
ad5ed80c 210 sub BUILD {
36c99105 211 my ( $self, $params ) = @_;
922a97e9 212 foreach my $employee ( @{ $self->employees || [] } ) {
213 $employee->employer($self);
ad5ed80c 214 }
215 }
216
922a97e9 217The C<BUILD> method is executed after type constraints are checked, so it is
218safe to assume that if C<< $self->employees >> has a value, it will be an
219array reference, and that the elements of that array reference will be
220C<Employee> objects.
4a6b74bd 221
222We also want to make sure that whenever the C<employees> attribute for
223a C<Company> is changed, we also update the C<employer> for each
224employee.
ad5ed80c 225
4a6b74bd 226To do this we can use an C<after> modifier:
ad5ed80c 227
228 after 'employees' => sub {
36c99105 229 my ( $self, $employees ) = @_;
922a97e9 230 foreach my $employee ( @{ $employees || [] } ) {
231 $employee->employer($self);
ad5ed80c 232 }
233 };
234
922a97e9 235Again, as with the C<BUILD> method, we know that the type constraint check has
236already happened, so we know that if C<$employees> is defined it will contain
172b4ff6 237an array reference of C<Employee> objects.
ad5ed80c 238
fdba9686 239The B<Person> class does not really demonstrate anything new. It has several
4a6b74bd 240C<required> attributes. It also has a C<predicate> method, which we
241first used in L<recipe 3|Moose::Cookbook::Basics::Recipe3>.
f1917f58 242
4a6b74bd 243The only new feature in the C<Employee> class is the C<override>
244method modifier:
f1917f58 245
246 override 'full_name' => sub {
247 my $self = shift;
36c99105 248 super() . ', ' . $self->title;
f1917f58 249 };
250
4a6b74bd 251This is just a sugary alternative to Perl's built in C<SUPER::>
252feature. However, there is one difference. You cannot pass any
19320607 253arguments to C<super>. Instead, Moose simply passes the same
4a6b74bd 254parameters that were passed to the method.
ad5ed80c 255
4a6b74bd 256A more detailed example of usage can be found in
dfdce4f1 257F<t/recipes/moose_cookbook_basics_recipe4.t>.
ad5ed80c 258
259=head1 CONCLUSION
260
4a6b74bd 261This recipe was intentionally longer and more complex. It illustrates
262how Moose classes can be used together with type constraints, as well
263as the density of information that you can get out of a small amount
264of typing when using Moose.
265
266This recipe also introduced the C<subtype> function, the C<required>
267attribute, and the C<override> method modifier.
ad5ed80c 268
4a6b74bd 269We will revisit type constraints in future recipes, and cover type
270coercion as well.
e08c54f5 271
172e0738 272=head1 FOOTNOTES
273
274=over 4
275
276=item (1)
277
6549b0d1 278The value being checked is also passed as the first argument to
4a6b74bd 279the C<where> block, so it can be accessed as C<$_[0]>.
172e0738 280
ad5ed80c 281=item (2)
282
4a6b74bd 283Note that C<ArrayRef[]> will not work. Moose will not parse this as a
284container type, and instead you will have a new type named
285"ArrayRef[]", which doesn't make any sense.
286
287=item (3)
288
c25e358a 289The C<BUILD> method is actually called by C<< Moose::Object->new >>. It climbs
290the object inheritance graph and calls any C<BUILD> methods it finds in the
291correct order.
ad5ed80c 292
172e0738 293=back
294
c79239a2 295=begin testing
296
297{
298 package Company;
299
300 sub get_employee_count { scalar @{(shift)->employees} }
301}
302
303use Scalar::Util 'isweak';
304
305my $ii;
b10dde3a 306is(
307 exception {
308 $ii = Company->new(
309 {
310 name => 'Infinity Interactive',
311 address => Address->new(
312 street => '565 Plandome Rd., Suite 307',
313 city => 'Manhasset',
314 state => 'NY',
315 zip_code => '11030'
c79239a2 316 ),
b10dde3a 317 employees => [
318 Employee->new(
319 first_name => 'Jeremy',
320 last_name => 'Shao',
321 title => 'President / Senior Consultant',
322 address => Address->new(
323 city => 'Manhasset', state => 'NY'
324 )
325 ),
326 Employee->new(
327 first_name => 'Tommy',
328 last_name => 'Lee',
329 title => 'Vice President / Senior Developer',
330 address =>
331 Address->new( city => 'New York', state => 'NY' )
332 ),
333 Employee->new(
334 first_name => 'Stevan',
335 middle_initial => 'C',
336 last_name => 'Little',
337 title => 'Senior Developer',
338 address =>
339 Address->new( city => 'Madison', state => 'CT' )
340 ),
341 ]
342 }
343 );
344 },
345 undef,
346 '... created the entire company successfully'
347);
348
c79239a2 349isa_ok( $ii, 'Company' );
350
351is( $ii->name, 'Infinity Interactive',
352 '... got the right name for the company' );
353
354isa_ok( $ii->address, 'Address' );
355is( $ii->address->street, '565 Plandome Rd., Suite 307',
356 '... got the right street address' );
357is( $ii->address->city, 'Manhasset', '... got the right city' );
358is( $ii->address->state, 'NY', '... got the right state' );
359is( $ii->address->zip_code, 11030, '... got the zip code' );
360
361is( $ii->get_employee_count, 3, '... got the right employee count' );
362
363# employee #1
364
365isa_ok( $ii->employees->[0], 'Employee' );
366isa_ok( $ii->employees->[0], 'Person' );
367
368is( $ii->employees->[0]->first_name, 'Jeremy',
369 '... got the right first name' );
370is( $ii->employees->[0]->last_name, 'Shao', '... got the right last name' );
371ok( !$ii->employees->[0]->has_middle_initial, '... no middle initial' );
372is( $ii->employees->[0]->middle_initial, undef,
373 '... got the right middle initial value' );
374is( $ii->employees->[0]->full_name,
375 'Jeremy Shao, President / Senior Consultant',
376 '... got the right full name' );
377is( $ii->employees->[0]->title, 'President / Senior Consultant',
378 '... got the right title' );
379is( $ii->employees->[0]->employer, $ii, '... got the right company' );
380ok( isweak( $ii->employees->[0]->{employer} ),
381 '... the company is a weak-ref' );
382
383isa_ok( $ii->employees->[0]->address, 'Address' );
384is( $ii->employees->[0]->address->city, 'Manhasset',
385 '... got the right city' );
386is( $ii->employees->[0]->address->state, 'NY', '... got the right state' );
387
388# employee #2
389
390isa_ok( $ii->employees->[1], 'Employee' );
391isa_ok( $ii->employees->[1], 'Person' );
392
393is( $ii->employees->[1]->first_name, 'Tommy',
394 '... got the right first name' );
395is( $ii->employees->[1]->last_name, 'Lee', '... got the right last name' );
396ok( !$ii->employees->[1]->has_middle_initial, '... no middle initial' );
397is( $ii->employees->[1]->middle_initial, undef,
398 '... got the right middle initial value' );
399is( $ii->employees->[1]->full_name,
400 'Tommy Lee, Vice President / Senior Developer',
401 '... got the right full name' );
402is( $ii->employees->[1]->title, 'Vice President / Senior Developer',
403 '... got the right title' );
404is( $ii->employees->[1]->employer, $ii, '... got the right company' );
405ok( isweak( $ii->employees->[1]->{employer} ),
406 '... the company is a weak-ref' );
407
408isa_ok( $ii->employees->[1]->address, 'Address' );
409is( $ii->employees->[1]->address->city, 'New York',
410 '... got the right city' );
411is( $ii->employees->[1]->address->state, 'NY', '... got the right state' );
412
413# employee #3
414
415isa_ok( $ii->employees->[2], 'Employee' );
416isa_ok( $ii->employees->[2], 'Person' );
417
418is( $ii->employees->[2]->first_name, 'Stevan',
419 '... got the right first name' );
420is( $ii->employees->[2]->last_name, 'Little', '... got the right last name' );
421ok( $ii->employees->[2]->has_middle_initial, '... got middle initial' );
422is( $ii->employees->[2]->middle_initial, 'C',
423 '... got the right middle initial value' );
424is( $ii->employees->[2]->full_name, 'Stevan C. Little, Senior Developer',
425 '... got the right full name' );
426is( $ii->employees->[2]->title, 'Senior Developer',
427 '... got the right title' );
428is( $ii->employees->[2]->employer, $ii, '... got the right company' );
429ok( isweak( $ii->employees->[2]->{employer} ),
430 '... the company is a weak-ref' );
431
432isa_ok( $ii->employees->[2]->address, 'Address' );
433is( $ii->employees->[2]->address->city, 'Madison', '... got the right city' );
434is( $ii->employees->[2]->address->state, 'CT', '... got the right state' );
435
436# create new company
437
438my $new_company
439 = Company->new( name => 'Infinity Interactive International' );
440isa_ok( $new_company, 'Company' );
441
442my $ii_employees = $ii->employees;
443foreach my $employee (@$ii_employees) {
444 is( $employee->employer, $ii, '... has the ii company' );
445}
446
447$new_company->employees($ii_employees);
448
449foreach my $employee ( @{ $new_company->employees } ) {
450 is( $employee->employer, $new_company,
451 '... has the different company now' );
452}
453
454## check some error conditions for the subtypes
455
b10dde3a 456isnt(
457 exception {
458 Address->new( street => {} ),;
459 },
460 undef,
461 '... we die correctly with bad args'
462);
463
464isnt(
465 exception {
466 Address->new( city => {} ),;
467 },
468 undef,
469 '... we die correctly with bad args'
470);
471
472isnt(
473 exception {
474 Address->new( state => 'British Columbia' ),;
475 },
476 undef,
477 '... we die correctly with bad args'
478);
479
480is(
481 exception {
482 Address->new( state => 'Connecticut' ),;
483 },
484 undef,
485 '... we live correctly with good args'
486);
487
488isnt(
489 exception {
490 Address->new( zip_code => 'AF5J6$' ),;
491 },
492 undef,
493 '... we die correctly with bad args'
494);
495
496is(
497 exception {
498 Address->new( zip_code => '06443' ),;
499 },
500 undef,
501 '... we live correctly with good args'
502);
503
504isnt(
505 exception {
506 Company->new(),;
507 },
508 undef,
509 '... we die correctly without good args'
510);
511
512is(
513 exception {
514 Company->new( name => 'Foo' ),;
515 },
516 undef,
517 '... we live correctly without good args'
518);
519
520isnt(
521 exception {
522 Company->new( name => 'Foo', employees => [ Person->new ] ),;
523 },
524 undef,
525 '... we die correctly with good args'
526);
527
528is(
529 exception {
530 Company->new( name => 'Foo', employees => [] ),;
531 },
532 undef,
533 '... we live correctly with good args'
534);
c79239a2 535
536=end testing
537
e08c54f5 538=cut