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