Use Test::Requires in recipe tests
[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;
318lives_ok {
319 $ii = Company->new(
320 {
321 name => 'Infinity Interactive',
322 address => Address->new(
323 street => '565 Plandome Rd., Suite 307',
324 city => 'Manhasset',
325 state => 'NY',
326 zip_code => '11030'
327 ),
328 employees => [
329 Employee->new(
330 first_name => 'Jeremy',
331 last_name => 'Shao',
332 title => 'President / Senior Consultant',
333 address =>
334 Address->new( city => 'Manhasset', state => 'NY' )
335 ),
336 Employee->new(
337 first_name => 'Tommy',
338 last_name => 'Lee',
339 title => 'Vice President / Senior Developer',
340 address =>
341 Address->new( city => 'New York', state => 'NY' )
342 ),
343 Employee->new(
344 first_name => 'Stevan',
345 middle_initial => 'C',
346 last_name => 'Little',
347 title => 'Senior Developer',
348 address =>
349 Address->new( city => 'Madison', state => 'CT' )
350 ),
351 ]
352 }
353 );
354}
355'... created the entire company successfully';
356isa_ok( $ii, 'Company' );
357
358is( $ii->name, 'Infinity Interactive',
359 '... got the right name for the company' );
360
361isa_ok( $ii->address, 'Address' );
362is( $ii->address->street, '565 Plandome Rd., Suite 307',
363 '... got the right street address' );
364is( $ii->address->city, 'Manhasset', '... got the right city' );
365is( $ii->address->state, 'NY', '... got the right state' );
366is( $ii->address->zip_code, 11030, '... got the zip code' );
367
368is( $ii->get_employee_count, 3, '... got the right employee count' );
369
370# employee #1
371
372isa_ok( $ii->employees->[0], 'Employee' );
373isa_ok( $ii->employees->[0], 'Person' );
374
375is( $ii->employees->[0]->first_name, 'Jeremy',
376 '... got the right first name' );
377is( $ii->employees->[0]->last_name, 'Shao', '... got the right last name' );
378ok( !$ii->employees->[0]->has_middle_initial, '... no middle initial' );
379is( $ii->employees->[0]->middle_initial, undef,
380 '... got the right middle initial value' );
381is( $ii->employees->[0]->full_name,
382 'Jeremy Shao, President / Senior Consultant',
383 '... got the right full name' );
384is( $ii->employees->[0]->title, 'President / Senior Consultant',
385 '... got the right title' );
386is( $ii->employees->[0]->employer, $ii, '... got the right company' );
387ok( isweak( $ii->employees->[0]->{employer} ),
388 '... the company is a weak-ref' );
389
390isa_ok( $ii->employees->[0]->address, 'Address' );
391is( $ii->employees->[0]->address->city, 'Manhasset',
392 '... got the right city' );
393is( $ii->employees->[0]->address->state, 'NY', '... got the right state' );
394
395# employee #2
396
397isa_ok( $ii->employees->[1], 'Employee' );
398isa_ok( $ii->employees->[1], 'Person' );
399
400is( $ii->employees->[1]->first_name, 'Tommy',
401 '... got the right first name' );
402is( $ii->employees->[1]->last_name, 'Lee', '... got the right last name' );
403ok( !$ii->employees->[1]->has_middle_initial, '... no middle initial' );
404is( $ii->employees->[1]->middle_initial, undef,
405 '... got the right middle initial value' );
406is( $ii->employees->[1]->full_name,
407 'Tommy Lee, Vice President / Senior Developer',
408 '... got the right full name' );
409is( $ii->employees->[1]->title, 'Vice President / Senior Developer',
410 '... got the right title' );
411is( $ii->employees->[1]->employer, $ii, '... got the right company' );
412ok( isweak( $ii->employees->[1]->{employer} ),
413 '... the company is a weak-ref' );
414
415isa_ok( $ii->employees->[1]->address, 'Address' );
416is( $ii->employees->[1]->address->city, 'New York',
417 '... got the right city' );
418is( $ii->employees->[1]->address->state, 'NY', '... got the right state' );
419
420# employee #3
421
422isa_ok( $ii->employees->[2], 'Employee' );
423isa_ok( $ii->employees->[2], 'Person' );
424
425is( $ii->employees->[2]->first_name, 'Stevan',
426 '... got the right first name' );
427is( $ii->employees->[2]->last_name, 'Little', '... got the right last name' );
428ok( $ii->employees->[2]->has_middle_initial, '... got middle initial' );
429is( $ii->employees->[2]->middle_initial, 'C',
430 '... got the right middle initial value' );
431is( $ii->employees->[2]->full_name, 'Stevan C. Little, Senior Developer',
432 '... got the right full name' );
433is( $ii->employees->[2]->title, 'Senior Developer',
434 '... got the right title' );
435is( $ii->employees->[2]->employer, $ii, '... got the right company' );
436ok( isweak( $ii->employees->[2]->{employer} ),
437 '... the company is a weak-ref' );
438
439isa_ok( $ii->employees->[2]->address, 'Address' );
440is( $ii->employees->[2]->address->city, 'Madison', '... got the right city' );
441is( $ii->employees->[2]->address->state, 'CT', '... got the right state' );
442
443# create new company
444
445my $new_company
446 = Company->new( name => 'Infinity Interactive International' );
447isa_ok( $new_company, 'Company' );
448
449my $ii_employees = $ii->employees;
450foreach my $employee (@$ii_employees) {
451 is( $employee->employer, $ii, '... has the ii company' );
452}
453
454$new_company->employees($ii_employees);
455
456foreach my $employee ( @{ $new_company->employees } ) {
457 is( $employee->employer, $new_company,
458 '... has the different company now' );
459}
460
461## check some error conditions for the subtypes
462
463dies_ok {
464 Address->new( street => {} ),;
465}
466'... we die correctly with bad args';
467
468dies_ok {
469 Address->new( city => {} ),;
470}
471'... we die correctly with bad args';
472
473dies_ok {
474 Address->new( state => 'British Columbia' ),;
475}
476'... we die correctly with bad args';
477
478lives_ok {
479 Address->new( state => 'Connecticut' ),;
480}
481'... we live correctly with good args';
482
483dies_ok {
484 Address->new( zip_code => 'AF5J6$' ),;
485}
486'... we die correctly with bad args';
487
488lives_ok {
489 Address->new( zip_code => '06443' ),;
490}
491'... we live correctly with good args';
492
493dies_ok {
494 Company->new(),;
495}
496'... we die correctly without good args';
497
498lives_ok {
499 Company->new( name => 'Foo' ),;
500}
501'... we live correctly without good args';
502
503dies_ok {
504 Company->new( name => 'Foo', employees => [ Person->new ] ),;
505}
506'... we die correctly with good args';
507
508lives_ok {
509 Company->new( name => 'Foo', employees => [] ),;
510}
511'... we live correctly with good args';
512
513=end testing
514
e08c54f5 515=cut