Add all our optional test deps as author_requires
[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 ) = @_;
922a97e9 59 foreach my $employee ( @{ $self->employees || [] } ) {
60 $employee->employer($self);
f1917f58 61 }
62 }
36c99105 63
f1917f58 64 after 'employees' => sub {
36c99105 65 my ( $self, $employees ) = @_;
922a97e9 66 foreach my $employee ( @{ $employees || [] } ) {
67 $employee->employer($self);
f1917f58 68 }
36c99105 69 };
70
471c4f09 71 package Person;
471c4f09 72 use Moose;
36c99105 73
74 has 'first_name' => ( is => 'rw', isa => 'Str', required => 1 );
75 has 'last_name' => ( is => 'rw', isa => 'Str', required => 1 );
76 has 'middle_initial' => (
77 is => 'rw', isa => 'Str',
78 predicate => 'has_middle_initial'
79 );
80 has 'address' => ( is => 'rw', isa => 'Address' );
81
471c4f09 82 sub full_name {
83 my $self = shift;
36c99105 84 return $self->first_name
85 . (
86 $self->has_middle_initial
87 ? ' ' . $self->middle_initial . '. '
88 : ' '
89 ) . $self->last_name;
471c4f09 90 }
36c99105 91
471c4f09 92 package Employee;
36c99105 93 use Moose;
94
471c4f09 95 extends 'Person';
36c99105 96
4a6b74bd 97 has 'title' => ( is => 'rw', isa => 'Str', required => 1 );
98 has 'employer' => ( is => 'rw', isa => 'Company', weak_ref => 1 );
36c99105 99
471c4f09 100 override 'full_name' => sub {
101 my $self = shift;
36c99105 102 super() . ', ' . $self->title;
471c4f09 103 };
2f04a0fc 104
471c4f09 105=head1 DESCRIPTION
106
4a6b74bd 107This recipe introduces the C<subtype> sugar function from
108L<Moose::Util::TypeConstraints>. The C<subtype> function lets you
109declaratively create type constraints without building an entire
110class.
172e0738 111
4a6b74bd 112In the recipe we also make use of L<Locale::US> and L<Regexp::Common>
cad0dd79 113to build constraints, showing how constraints can make use of existing
114CPAN tools for data validation.
36c99105 115
16fb3624 116Finally, we introduce the C<required> attribute option.
4a6b74bd 117
21ec1978 118In the C<Address> class we define two subtypes. The first uses the
19320607 119L<Locale::US> module to check the validity of a state. It accepts
4a6b74bd 120either a state abbreviation of full name.
121
122A state will be passed in as a string, so we make our C<USState> type
123a subtype of Moose's builtin C<Str> type. This is done using the C<as>
124sugar. The actual constraint is defined using C<where>. This function
125accepts a single subroutine reference. That subroutine will be called
126with the value to be checked in C<$_> (1). It is expected to return a
127true or false value indicating whether the value is valid for the
128type.
172e0738 129
4a6b74bd 130We can now use the C<USState> type just like Moose's builtin types:
172e0738 131
36c99105 132 has 'state' => ( is => 'rw', isa => 'USState' );
172e0738 133
4a6b74bd 134When the C<state> attribute is set, the value is checked against the
135C<USState> constraint. If the value is not valid, an exception will be
136thrown.
172e0738 137
4a6b74bd 138The next C<subtype>, C<USZipCode>, uses
139L<Regexp::Common>. L<Regexp::Common> includes a regex for validating
140US zip codes. We use this constraint for the C<zip_code> attribute.
172e0738 141
0b3811a6 142 subtype 'USZipCode'
172e0738 143 => as Value
144 => where {
36c99105 145 /^$RE{zip}{US}{-extended => 'allow'}$/;
146 };
172e0738 147
4a6b74bd 148Using a subtype instead of requiring a class for each type greatly
149simplifies the code. We don't really need a class for these types, as
150they're just strings, but we do want to ensure that they're valid.
151
152The type constraints we created are reusable. Type constraints are
153stored by name in a global registry. This means that we can refer to
154them in other classes. Because the registry is global, we do recommend
155that you use some sort of pseudo-namespacing in real applications,
156like C<MyApp.Type.USState>.
157
158These two subtypes allow us to define a simple C<Address> class.
172e0738 159
4a6b74bd 160Then we define our C<Company> class, which has an address. As we saw
161in earlier recipes, Moose automatically creates a type constraint for
162each our classes, so we can use that for the C<Company> class's
163C<address> attribute:
172e0738 164
36c99105 165 has 'address' => ( is => 'rw', isa => 'Address' );
172e0738 166
4a6b74bd 167A company also needs a name:
172e0738 168
36c99105 169 has 'name' => ( is => 'rw', isa => 'Str', required => 1 );
172e0738 170
16fb3624 171This introduces a new attribute option, C<required>. If an attribute
172is required, then it must be passed to the class's constructor, or an
173exception will be thrown. It's important to understand that a
174C<required> attribute can still be false or C<undef>, if its type
175constraint allows that.
f1917f58 176
4a6b74bd 177The next attribute, C<employees>, uses a I<parameterized> type
178constraint:
36c99105 179
180 has 'employees' => ( is => 'rw', isa => 'ArrayRef[Employee]' );
07cde929 181
4a6b74bd 182This constraint says that C<employees> must be an array reference
183where each element of the array is an C<Employee> object. It's worth
184noting that an I<empty> array reference also satisfies this
185constraint.
186
cad0dd79 187Parameterizable type constraints (or "container types"), such as
4a6b74bd 188C<ArrayRef[`a]>, can be made more specific with a type parameter. In
189fact, we can arbitrarily nest these types, producing something like
190C<HashRef[ArrayRef[Int]]>. However, you can also just use the type by
191itself, so C<ArrayRef> is legal. (2)
192
193If you jump down to the definition of the C<Employee> class, you will
194see that it has an C<employer> attribute.
195
196When we set the C<employees> for a C<Company> we want to make sure
197that each of these employee objects refers back to the right
198C<Company> in its C<employer> attribute.
199
200To do that, we need to hook into object construction. Moose lets us do
201this by writing a C<BUILD> method in our class. When your class
202defined a C<BUILD> method, it will be called immediately after an
203object construction, but before the object is returned to the caller
204(3).
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
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
c79239a2 257F<t/000_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
289The C<BUILD> method is actually called by C<< Moose::Object->BUILDALL
290>>, which is called by C<< Moose::Object->new >>. The C<BUILDALL>
291method climbs the object inheritance graph and calls any C<BUILD>
292methods it finds in the correct order.
ad5ed80c 293
172e0738 294=back
295
8c3d5c88 296=head1 AUTHORS
471c4f09 297
298Stevan Little E<lt>stevan@iinteractive.comE<gt>
299
8c3d5c88 300Dave Rolsky E<lt>autarch@urth.orgE<gt>
301
471c4f09 302=head1 COPYRIGHT AND LICENSE
303
7e0492d3 304Copyright 2006-2010 by Infinity Interactive, Inc.
471c4f09 305
306L<http://www.iinteractive.com>
307
308This library is free software; you can redistribute it and/or modify
309it under the same terms as Perl itself.
310
c79239a2 311=begin testing
312
313{
314 package Company;
315
316 sub get_employee_count { scalar @{(shift)->employees} }
317}
318
319use Scalar::Util 'isweak';
320
321my $ii;
322lives_ok {
323 $ii = Company->new(
324 {
325 name => 'Infinity Interactive',
326 address => Address->new(
327 street => '565 Plandome Rd., Suite 307',
328 city => 'Manhasset',
329 state => 'NY',
330 zip_code => '11030'
331 ),
332 employees => [
333 Employee->new(
334 first_name => 'Jeremy',
335 last_name => 'Shao',
336 title => 'President / Senior Consultant',
337 address =>
338 Address->new( city => 'Manhasset', state => 'NY' )
339 ),
340 Employee->new(
341 first_name => 'Tommy',
342 last_name => 'Lee',
343 title => 'Vice President / Senior Developer',
344 address =>
345 Address->new( city => 'New York', state => 'NY' )
346 ),
347 Employee->new(
348 first_name => 'Stevan',
349 middle_initial => 'C',
350 last_name => 'Little',
351 title => 'Senior Developer',
352 address =>
353 Address->new( city => 'Madison', state => 'CT' )
354 ),
355 ]
356 }
357 );
358}
359'... created the entire company successfully';
360isa_ok( $ii, 'Company' );
361
362is( $ii->name, 'Infinity Interactive',
363 '... got the right name for the company' );
364
365isa_ok( $ii->address, 'Address' );
366is( $ii->address->street, '565 Plandome Rd., Suite 307',
367 '... got the right street address' );
368is( $ii->address->city, 'Manhasset', '... got the right city' );
369is( $ii->address->state, 'NY', '... got the right state' );
370is( $ii->address->zip_code, 11030, '... got the zip code' );
371
372is( $ii->get_employee_count, 3, '... got the right employee count' );
373
374# employee #1
375
376isa_ok( $ii->employees->[0], 'Employee' );
377isa_ok( $ii->employees->[0], 'Person' );
378
379is( $ii->employees->[0]->first_name, 'Jeremy',
380 '... got the right first name' );
381is( $ii->employees->[0]->last_name, 'Shao', '... got the right last name' );
382ok( !$ii->employees->[0]->has_middle_initial, '... no middle initial' );
383is( $ii->employees->[0]->middle_initial, undef,
384 '... got the right middle initial value' );
385is( $ii->employees->[0]->full_name,
386 'Jeremy Shao, President / Senior Consultant',
387 '... got the right full name' );
388is( $ii->employees->[0]->title, 'President / Senior Consultant',
389 '... got the right title' );
390is( $ii->employees->[0]->employer, $ii, '... got the right company' );
391ok( isweak( $ii->employees->[0]->{employer} ),
392 '... the company is a weak-ref' );
393
394isa_ok( $ii->employees->[0]->address, 'Address' );
395is( $ii->employees->[0]->address->city, 'Manhasset',
396 '... got the right city' );
397is( $ii->employees->[0]->address->state, 'NY', '... got the right state' );
398
399# employee #2
400
401isa_ok( $ii->employees->[1], 'Employee' );
402isa_ok( $ii->employees->[1], 'Person' );
403
404is( $ii->employees->[1]->first_name, 'Tommy',
405 '... got the right first name' );
406is( $ii->employees->[1]->last_name, 'Lee', '... got the right last name' );
407ok( !$ii->employees->[1]->has_middle_initial, '... no middle initial' );
408is( $ii->employees->[1]->middle_initial, undef,
409 '... got the right middle initial value' );
410is( $ii->employees->[1]->full_name,
411 'Tommy Lee, Vice President / Senior Developer',
412 '... got the right full name' );
413is( $ii->employees->[1]->title, 'Vice President / Senior Developer',
414 '... got the right title' );
415is( $ii->employees->[1]->employer, $ii, '... got the right company' );
416ok( isweak( $ii->employees->[1]->{employer} ),
417 '... the company is a weak-ref' );
418
419isa_ok( $ii->employees->[1]->address, 'Address' );
420is( $ii->employees->[1]->address->city, 'New York',
421 '... got the right city' );
422is( $ii->employees->[1]->address->state, 'NY', '... got the right state' );
423
424# employee #3
425
426isa_ok( $ii->employees->[2], 'Employee' );
427isa_ok( $ii->employees->[2], 'Person' );
428
429is( $ii->employees->[2]->first_name, 'Stevan',
430 '... got the right first name' );
431is( $ii->employees->[2]->last_name, 'Little', '... got the right last name' );
432ok( $ii->employees->[2]->has_middle_initial, '... got middle initial' );
433is( $ii->employees->[2]->middle_initial, 'C',
434 '... got the right middle initial value' );
435is( $ii->employees->[2]->full_name, 'Stevan C. Little, Senior Developer',
436 '... got the right full name' );
437is( $ii->employees->[2]->title, 'Senior Developer',
438 '... got the right title' );
439is( $ii->employees->[2]->employer, $ii, '... got the right company' );
440ok( isweak( $ii->employees->[2]->{employer} ),
441 '... the company is a weak-ref' );
442
443isa_ok( $ii->employees->[2]->address, 'Address' );
444is( $ii->employees->[2]->address->city, 'Madison', '... got the right city' );
445is( $ii->employees->[2]->address->state, 'CT', '... got the right state' );
446
447# create new company
448
449my $new_company
450 = Company->new( name => 'Infinity Interactive International' );
451isa_ok( $new_company, 'Company' );
452
453my $ii_employees = $ii->employees;
454foreach my $employee (@$ii_employees) {
455 is( $employee->employer, $ii, '... has the ii company' );
456}
457
458$new_company->employees($ii_employees);
459
460foreach my $employee ( @{ $new_company->employees } ) {
461 is( $employee->employer, $new_company,
462 '... has the different company now' );
463}
464
465## check some error conditions for the subtypes
466
467dies_ok {
468 Address->new( street => {} ),;
469}
470'... we die correctly with bad args';
471
472dies_ok {
473 Address->new( city => {} ),;
474}
475'... we die correctly with bad args';
476
477dies_ok {
478 Address->new( state => 'British Columbia' ),;
479}
480'... we die correctly with bad args';
481
482lives_ok {
483 Address->new( state => 'Connecticut' ),;
484}
485'... we live correctly with good args';
486
487dies_ok {
488 Address->new( zip_code => 'AF5J6$' ),;
489}
490'... we die correctly with bad args';
491
492lives_ok {
493 Address->new( zip_code => '06443' ),;
494}
495'... we live correctly with good args';
496
497dies_ok {
498 Company->new(),;
499}
500'... we die correctly without good args';
501
502lives_ok {
503 Company->new( name => 'Foo' ),;
504}
505'... we live correctly without good args';
506
507dies_ok {
508 Company->new( name => 'Foo', employees => [ Person->new ] ),;
509}
510'... we die correctly with good args';
511
512lives_ok {
513 Company->new( name => 'Foo', employees => [] ),;
514}
515'... we live correctly with good args';
516
517=end testing
518
e08c54f5 519=cut