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