Dzil-ize all the .pod files so they can be pod-woven
[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
151stored by name in a global registry. This means that we can refer to
152them in other classes. Because the registry is global, we do recommend
153that you use some sort of pseudo-namespacing in real applications,
154like C<MyApp.Type.USState>.
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
200defined a C<BUILD> method, it will be called immediately after an
201object construction, but before the object is returned to the caller
202(3).
203
204The C<Company> class uses the C<BUILD> method to ensure that each
205employee of a company has the proper C<Company> object in its
206C<employer> attribute:
36c99105 207
ad5ed80c 208 sub BUILD {
36c99105 209 my ( $self, $params ) = @_;
922a97e9 210 foreach my $employee ( @{ $self->employees || [] } ) {
211 $employee->employer($self);
ad5ed80c 212 }
213 }
214
922a97e9 215The C<BUILD> method is executed after type constraints are checked, so it is
216safe to assume that if C<< $self->employees >> has a value, it will be an
217array reference, and that the elements of that array reference will be
218C<Employee> objects.
4a6b74bd 219
220We also want to make sure that whenever the C<employees> attribute for
221a C<Company> is changed, we also update the C<employer> for each
222employee.
ad5ed80c 223
4a6b74bd 224To do this we can use an C<after> modifier:
ad5ed80c 225
226 after 'employees' => sub {
36c99105 227 my ( $self, $employees ) = @_;
922a97e9 228 foreach my $employee ( @{ $employees || [] } ) {
229 $employee->employer($self);
ad5ed80c 230 }
231 };
232
922a97e9 233Again, as with the C<BUILD> method, we know that the type constraint check has
234already happened, so we know that if C<$employees> is defined it will contain
235an array reference of C<Employee> objects..
ad5ed80c 236
fdba9686 237The B<Person> class does not really demonstrate anything new. It has several
4a6b74bd 238C<required> attributes. It also has a C<predicate> method, which we
239first used in L<recipe 3|Moose::Cookbook::Basics::Recipe3>.
f1917f58 240
4a6b74bd 241The only new feature in the C<Employee> class is the C<override>
242method modifier:
f1917f58 243
244 override 'full_name' => sub {
245 my $self = shift;
36c99105 246 super() . ', ' . $self->title;
f1917f58 247 };
248
4a6b74bd 249This is just a sugary alternative to Perl's built in C<SUPER::>
250feature. However, there is one difference. You cannot pass any
19320607 251arguments to C<super>. Instead, Moose simply passes the same
4a6b74bd 252parameters that were passed to the method.
ad5ed80c 253
4a6b74bd 254A more detailed example of usage can be found in
c79239a2 255F<t/000_recipes/moose_cookbook_basics_recipe4.t>.
ad5ed80c 256
257=head1 CONCLUSION
258
4a6b74bd 259This recipe was intentionally longer and more complex. It illustrates
260how Moose classes can be used together with type constraints, as well
261as the density of information that you can get out of a small amount
262of typing when using Moose.
263
264This recipe also introduced the C<subtype> function, the C<required>
265attribute, and the C<override> method modifier.
ad5ed80c 266
4a6b74bd 267We will revisit type constraints in future recipes, and cover type
268coercion as well.
e08c54f5 269
172e0738 270=head1 FOOTNOTES
271
272=over 4
273
274=item (1)
275
6549b0d1 276The value being checked is also passed as the first argument to
4a6b74bd 277the C<where> block, so it can be accessed as C<$_[0]>.
172e0738 278
ad5ed80c 279=item (2)
280
4a6b74bd 281Note that C<ArrayRef[]> will not work. Moose will not parse this as a
282container type, and instead you will have a new type named
283"ArrayRef[]", which doesn't make any sense.
284
285=item (3)
286
287The C<BUILD> method is actually called by C<< Moose::Object->BUILDALL
288>>, which is called by C<< Moose::Object->new >>. The C<BUILDALL>
289method climbs the object inheritance graph and calls any C<BUILD>
290methods it finds in the correct order.
ad5ed80c 291
172e0738 292=back
293
c79239a2 294=begin testing
295
296{
297 package Company;
298
299 sub get_employee_count { scalar @{(shift)->employees} }
300}
301
302use Scalar::Util 'isweak';
303
304my $ii;
b10dde3a 305is(
306 exception {
307 $ii = Company->new(
308 {
309 name => 'Infinity Interactive',
310 address => Address->new(
311 street => '565 Plandome Rd., Suite 307',
312 city => 'Manhasset',
313 state => 'NY',
314 zip_code => '11030'
c79239a2 315 ),
b10dde3a 316 employees => [
317 Employee->new(
318 first_name => 'Jeremy',
319 last_name => 'Shao',
320 title => 'President / Senior Consultant',
321 address => Address->new(
322 city => 'Manhasset', state => 'NY'
323 )
324 ),
325 Employee->new(
326 first_name => 'Tommy',
327 last_name => 'Lee',
328 title => 'Vice President / Senior Developer',
329 address =>
330 Address->new( city => 'New York', state => 'NY' )
331 ),
332 Employee->new(
333 first_name => 'Stevan',
334 middle_initial => 'C',
335 last_name => 'Little',
336 title => 'Senior Developer',
337 address =>
338 Address->new( city => 'Madison', state => 'CT' )
339 ),
340 ]
341 }
342 );
343 },
344 undef,
345 '... created the entire company successfully'
346);
347
c79239a2 348isa_ok( $ii, 'Company' );
349
350is( $ii->name, 'Infinity Interactive',
351 '... got the right name for the company' );
352
353isa_ok( $ii->address, 'Address' );
354is( $ii->address->street, '565 Plandome Rd., Suite 307',
355 '... got the right street address' );
356is( $ii->address->city, 'Manhasset', '... got the right city' );
357is( $ii->address->state, 'NY', '... got the right state' );
358is( $ii->address->zip_code, 11030, '... got the zip code' );
359
360is( $ii->get_employee_count, 3, '... got the right employee count' );
361
362# employee #1
363
364isa_ok( $ii->employees->[0], 'Employee' );
365isa_ok( $ii->employees->[0], 'Person' );
366
367is( $ii->employees->[0]->first_name, 'Jeremy',
368 '... got the right first name' );
369is( $ii->employees->[0]->last_name, 'Shao', '... got the right last name' );
370ok( !$ii->employees->[0]->has_middle_initial, '... no middle initial' );
371is( $ii->employees->[0]->middle_initial, undef,
372 '... got the right middle initial value' );
373is( $ii->employees->[0]->full_name,
374 'Jeremy Shao, President / Senior Consultant',
375 '... got the right full name' );
376is( $ii->employees->[0]->title, 'President / Senior Consultant',
377 '... got the right title' );
378is( $ii->employees->[0]->employer, $ii, '... got the right company' );
379ok( isweak( $ii->employees->[0]->{employer} ),
380 '... the company is a weak-ref' );
381
382isa_ok( $ii->employees->[0]->address, 'Address' );
383is( $ii->employees->[0]->address->city, 'Manhasset',
384 '... got the right city' );
385is( $ii->employees->[0]->address->state, 'NY', '... got the right state' );
386
387# employee #2
388
389isa_ok( $ii->employees->[1], 'Employee' );
390isa_ok( $ii->employees->[1], 'Person' );
391
392is( $ii->employees->[1]->first_name, 'Tommy',
393 '... got the right first name' );
394is( $ii->employees->[1]->last_name, 'Lee', '... got the right last name' );
395ok( !$ii->employees->[1]->has_middle_initial, '... no middle initial' );
396is( $ii->employees->[1]->middle_initial, undef,
397 '... got the right middle initial value' );
398is( $ii->employees->[1]->full_name,
399 'Tommy Lee, Vice President / Senior Developer',
400 '... got the right full name' );
401is( $ii->employees->[1]->title, 'Vice President / Senior Developer',
402 '... got the right title' );
403is( $ii->employees->[1]->employer, $ii, '... got the right company' );
404ok( isweak( $ii->employees->[1]->{employer} ),
405 '... the company is a weak-ref' );
406
407isa_ok( $ii->employees->[1]->address, 'Address' );
408is( $ii->employees->[1]->address->city, 'New York',
409 '... got the right city' );
410is( $ii->employees->[1]->address->state, 'NY', '... got the right state' );
411
412# employee #3
413
414isa_ok( $ii->employees->[2], 'Employee' );
415isa_ok( $ii->employees->[2], 'Person' );
416
417is( $ii->employees->[2]->first_name, 'Stevan',
418 '... got the right first name' );
419is( $ii->employees->[2]->last_name, 'Little', '... got the right last name' );
420ok( $ii->employees->[2]->has_middle_initial, '... got middle initial' );
421is( $ii->employees->[2]->middle_initial, 'C',
422 '... got the right middle initial value' );
423is( $ii->employees->[2]->full_name, 'Stevan C. Little, Senior Developer',
424 '... got the right full name' );
425is( $ii->employees->[2]->title, 'Senior Developer',
426 '... got the right title' );
427is( $ii->employees->[2]->employer, $ii, '... got the right company' );
428ok( isweak( $ii->employees->[2]->{employer} ),
429 '... the company is a weak-ref' );
430
431isa_ok( $ii->employees->[2]->address, 'Address' );
432is( $ii->employees->[2]->address->city, 'Madison', '... got the right city' );
433is( $ii->employees->[2]->address->state, 'CT', '... got the right state' );
434
435# create new company
436
437my $new_company
438 = Company->new( name => 'Infinity Interactive International' );
439isa_ok( $new_company, 'Company' );
440
441my $ii_employees = $ii->employees;
442foreach my $employee (@$ii_employees) {
443 is( $employee->employer, $ii, '... has the ii company' );
444}
445
446$new_company->employees($ii_employees);
447
448foreach my $employee ( @{ $new_company->employees } ) {
449 is( $employee->employer, $new_company,
450 '... has the different company now' );
451}
452
453## check some error conditions for the subtypes
454
b10dde3a 455isnt(
456 exception {
457 Address->new( street => {} ),;
458 },
459 undef,
460 '... we die correctly with bad args'
461);
462
463isnt(
464 exception {
465 Address->new( city => {} ),;
466 },
467 undef,
468 '... we die correctly with bad args'
469);
470
471isnt(
472 exception {
473 Address->new( state => 'British Columbia' ),;
474 },
475 undef,
476 '... we die correctly with bad args'
477);
478
479is(
480 exception {
481 Address->new( state => 'Connecticut' ),;
482 },
483 undef,
484 '... we live correctly with good args'
485);
486
487isnt(
488 exception {
489 Address->new( zip_code => 'AF5J6$' ),;
490 },
491 undef,
492 '... we die correctly with bad args'
493);
494
495is(
496 exception {
497 Address->new( zip_code => '06443' ),;
498 },
499 undef,
500 '... we live correctly with good args'
501);
502
503isnt(
504 exception {
505 Company->new(),;
506 },
507 undef,
508 '... we die correctly without good args'
509);
510
511is(
512 exception {
513 Company->new( name => 'Foo' ),;
514 },
515 undef,
516 '... we live correctly without good args'
517);
518
519isnt(
520 exception {
521 Company->new( name => 'Foo', employees => [ Person->new ] ),;
522 },
523 undef,
524 '... we die correctly with good args'
525);
526
527is(
528 exception {
529 Company->new( name => 'Foo', employees => [] ),;
530 },
531 undef,
532 '... we live correctly with good args'
533);
c79239a2 534
535=end testing
536
e08c54f5 537=cut