1 package Moose::Cookbook::Basics::Recipe4;
3 # ABSTRACT: Subtypes, and modeling a simple B<Company> class hierarchy
14 'Regexp::Common' => '0',
23 use Moose::Util::TypeConstraints;
26 use Regexp::Common 'zip';
28 my $STATES = Locale::US->new;
32 ( exists $STATES->{code2state}{ uc($_) }
33 || exists $STATES->{state2code}{ uc($_) } );
39 /^$RE{zip}{US}{-extended => 'allow'}$/;
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' );
49 use Moose::Util::TypeConstraints;
51 has 'name' => ( is => 'rw', isa => 'Str', required => 1 );
52 has 'address' => ( is => 'rw', isa => 'Address' );
55 isa => 'ArrayRef[Employee]',
56 default => sub { [] },
60 my ( $self, $params ) = @_;
61 foreach my $employee ( @{ $self->employees } ) {
62 $employee->employer($self);
66 after 'employees' => sub {
67 my ( $self, $employees ) = @_;
68 return unless $employees;
69 foreach my $employee ( @$employees ) {
70 $employee->employer($self);
77 has 'first_name' => ( is => 'rw', isa => 'Str', required => 1 );
78 has 'last_name' => ( is => 'rw', isa => 'Str', required => 1 );
79 has 'middle_initial' => (
80 is => 'rw', isa => 'Str',
81 predicate => 'has_middle_initial'
83 has 'address' => ( is => 'rw', isa => 'Address' );
87 return $self->first_name
89 $self->has_middle_initial
90 ? ' ' . $self->middle_initial . '. '
100 has 'title' => ( is => 'rw', isa => 'Str', required => 1 );
101 has 'employer' => ( is => 'rw', isa => 'Company', weak_ref => 1 );
103 override 'full_name' => sub {
105 super() . ', ' . $self->title;
110 This recipe introduces the C<subtype> sugar function from
111 L<Moose::Util::TypeConstraints>. The C<subtype> function lets you
112 declaratively create type constraints without building an entire
115 In the recipe we also make use of L<Locale::US> and L<Regexp::Common>
116 to build constraints, showing how constraints can make use of existing
117 CPAN tools for data validation.
119 Finally, we introduce the C<required> attribute option.
121 In the C<Address> class we define two subtypes. The first uses the
122 L<Locale::US> module to check the validity of a state. It accepts
123 either a state abbreviation of full name.
125 A state will be passed in as a string, so we make our C<USState> type
126 a subtype of Moose's builtin C<Str> type. This is done using the C<as>
127 sugar. The actual constraint is defined using C<where>. This function
128 accepts a single subroutine reference. That subroutine will be called
129 with the value to be checked in C<$_> (1). It is expected to return a
130 true or false value indicating whether the value is valid for the
133 We can now use the C<USState> type just like Moose's builtin types:
135 has 'state' => ( is => 'rw', isa => 'USState' );
137 When the C<state> attribute is set, the value is checked against the
138 C<USState> constraint. If the value is not valid, an exception will be
141 The next C<subtype>, C<USZipCode>, uses
142 L<Regexp::Common>. L<Regexp::Common> includes a regex for validating
143 US zip codes. We use this constraint for the C<zip_code> attribute.
148 /^$RE{zip}{US}{-extended => 'allow'}$/;
151 Using a subtype instead of requiring a class for each type greatly
152 simplifies the code. We don't really need a class for these types, as
153 they're just strings, but we do want to ensure that they're valid.
155 The type constraints we created are reusable. Type constraints are
156 stored by name in a global registry, which means that we can refer to
157 them in other classes. Because the registry is global, we do recommend
158 that you use some sort of namespacing in real applications,
159 like C<MyApp::Type::USState> (just as you would do with class names).
161 These two subtypes allow us to define a simple C<Address> class.
163 Then we define our C<Company> class, which has an address. As we saw
164 in earlier recipes, Moose automatically creates a type constraint for
165 each our classes, so we can use that for the C<Company> class's
166 C<address> attribute:
168 has 'address' => ( is => 'rw', isa => 'Address' );
170 A company also needs a name:
172 has 'name' => ( is => 'rw', isa => 'Str', required => 1 );
174 This introduces a new attribute option, C<required>. If an attribute
175 is required, then it must be passed to the class's constructor, or an
176 exception will be thrown. It's important to understand that a
177 C<required> attribute can still be false or C<undef>, if its type
178 constraint allows that.
180 The next attribute, C<employees>, uses a I<parameterized> type
185 isa => 'ArrayRef[Employee]'
186 default => sub { [] },
189 This constraint says that C<employees> must be an array reference
190 where each element of the array is an C<Employee> object. It's worth
191 noting that an I<empty> array reference also satisfies this
192 constraint, such as the value given as the default here.
194 Parameterizable type constraints (or "container types"), such as
195 C<ArrayRef[`a]>, can be made more specific with a type parameter. In
196 fact, we can arbitrarily nest these types, producing something like
197 C<HashRef[ArrayRef[Int]]>. However, you can also just use the type by
198 itself, so C<ArrayRef> is legal. (2)
200 If you jump down to the definition of the C<Employee> class, you will
201 see that it has an C<employer> attribute.
203 When we set the C<employees> for a C<Company> we want to make sure
204 that each of these employee objects refers back to the right
205 C<Company> in its C<employer> attribute.
207 To do that, we need to hook into object construction. Moose lets us do
208 this by writing a C<BUILD> method in our class. When your class
209 defines a C<BUILD> method, it will be called by the constructor
210 immediately after object construction, but before the object is returned
211 to the caller. Note that all C<BUILD> methods in your class hierarchy
212 will be called automatically; there is no need to (and you should not)
213 call the superclass C<BUILD> method.
215 The C<Company> class uses the C<BUILD> method to ensure that each
216 employee of a company has the proper C<Company> object in its
217 C<employer> attribute:
220 my ( $self, $params ) = @_;
221 foreach my $employee ( @{ $self->employees } ) {
222 $employee->employer($self);
226 The C<BUILD> method is executed after type constraints are checked, so it is
227 safe to assume that if C<< $self->employees >> has a value, it will be an
228 array reference, and that the elements of that array reference will be
231 We also want to make sure that whenever the C<employees> attribute for
232 a C<Company> is changed, we also update the C<employer> for each
235 To do this we can use an C<after> modifier:
237 after 'employees' => sub {
238 my ( $self, $employees ) = @_;
239 return unless $employees;
240 foreach my $employee ( @$employees ) {
241 $employee->employer($self);
245 Again, as with the C<BUILD> method, we know that the type constraint check has
246 already happened, so we know that if C<$employees> is defined it will contain
247 an array reference of C<Employee> objects.
249 Note that C<employees> is a read/write accessor, so we must return early if
250 it's called as a reader.
252 The B<Person> class does not really demonstrate anything new. It has several
253 C<required> attributes. It also has a C<predicate> method, which we
254 first used in L<recipe 3|Moose::Cookbook::Basics::Recipe3>.
256 The only new feature in the C<Employee> class is the C<override>
259 override 'full_name' => sub {
261 super() . ', ' . $self->title;
264 This is just a sugary alternative to Perl's built in C<SUPER::>
265 feature. However, there is one difference. You cannot pass any
266 arguments to C<super>. Instead, Moose simply passes the same
267 parameters that were passed to the method.
269 A more detailed example of usage can be found in
270 F<t/recipes/moose_cookbook_basics_recipe4.t>.
274 This recipe was intentionally longer and more complex. It illustrates
275 how Moose classes can be used together with type constraints, as well
276 as the density of information that you can get out of a small amount
277 of typing when using Moose.
279 This recipe also introduced the C<subtype> function, the C<required>
280 attribute, and the C<override> method modifier.
282 We will revisit type constraints in future recipes, and cover type
291 The value being checked is also passed as the first argument to
292 the C<where> block, so it can be accessed as C<$_[0]>.
296 Note that C<ArrayRef[]> will not work. Moose will not parse this as a
297 container type, and instead you will have a new type named
298 "ArrayRef[]", which doesn't make any sense.
307 sub get_employee_count { scalar @{(shift)->employees} }
310 use Scalar::Util 'isweak';
317 name => 'Infinity Interactive',
318 address => Address->new(
319 street => '565 Plandome Rd., Suite 307',
326 first_name => 'Jeremy',
328 title => 'President / Senior Consultant',
329 address => Address->new(
330 city => 'Manhasset', state => 'NY'
334 first_name => 'Tommy',
336 title => 'Vice President / Senior Developer',
338 Address->new( city => 'New York', state => 'NY' )
341 first_name => 'Stevan',
342 middle_initial => 'C',
343 last_name => 'Little',
344 title => 'Senior Developer',
346 Address->new( city => 'Madison', state => 'CT' )
353 '... created the entire company successfully'
356 isa_ok( $ii, 'Company' );
358 is( $ii->name, 'Infinity Interactive',
359 '... got the right name for the company' );
361 isa_ok( $ii->address, 'Address' );
362 is( $ii->address->street, '565 Plandome Rd., Suite 307',
363 '... got the right street address' );
364 is( $ii->address->city, 'Manhasset', '... got the right city' );
365 is( $ii->address->state, 'NY', '... got the right state' );
366 is( $ii->address->zip_code, 11030, '... got the zip code' );
368 is( $ii->get_employee_count, 3, '... got the right employee count' );
372 isa_ok( $ii->employees->[0], 'Employee' );
373 isa_ok( $ii->employees->[0], 'Person' );
375 is( $ii->employees->[0]->first_name, 'Jeremy',
376 '... got the right first name' );
377 is( $ii->employees->[0]->last_name, 'Shao', '... got the right last name' );
378 ok( !$ii->employees->[0]->has_middle_initial, '... no middle initial' );
379 is( $ii->employees->[0]->middle_initial, undef,
380 '... got the right middle initial value' );
381 is( $ii->employees->[0]->full_name,
382 'Jeremy Shao, President / Senior Consultant',
383 '... got the right full name' );
384 is( $ii->employees->[0]->title, 'President / Senior Consultant',
385 '... got the right title' );
386 is( $ii->employees->[0]->employer, $ii, '... got the right company' );
387 ok( isweak( $ii->employees->[0]->{employer} ),
388 '... the company is a weak-ref' );
390 isa_ok( $ii->employees->[0]->address, 'Address' );
391 is( $ii->employees->[0]->address->city, 'Manhasset',
392 '... got the right city' );
393 is( $ii->employees->[0]->address->state, 'NY', '... got the right state' );
397 isa_ok( $ii->employees->[1], 'Employee' );
398 isa_ok( $ii->employees->[1], 'Person' );
400 is( $ii->employees->[1]->first_name, 'Tommy',
401 '... got the right first name' );
402 is( $ii->employees->[1]->last_name, 'Lee', '... got the right last name' );
403 ok( !$ii->employees->[1]->has_middle_initial, '... no middle initial' );
404 is( $ii->employees->[1]->middle_initial, undef,
405 '... got the right middle initial value' );
406 is( $ii->employees->[1]->full_name,
407 'Tommy Lee, Vice President / Senior Developer',
408 '... got the right full name' );
409 is( $ii->employees->[1]->title, 'Vice President / Senior Developer',
410 '... got the right title' );
411 is( $ii->employees->[1]->employer, $ii, '... got the right company' );
412 ok( isweak( $ii->employees->[1]->{employer} ),
413 '... the company is a weak-ref' );
415 isa_ok( $ii->employees->[1]->address, 'Address' );
416 is( $ii->employees->[1]->address->city, 'New York',
417 '... got the right city' );
418 is( $ii->employees->[1]->address->state, 'NY', '... got the right state' );
422 isa_ok( $ii->employees->[2], 'Employee' );
423 isa_ok( $ii->employees->[2], 'Person' );
425 is( $ii->employees->[2]->first_name, 'Stevan',
426 '... got the right first name' );
427 is( $ii->employees->[2]->last_name, 'Little', '... got the right last name' );
428 ok( $ii->employees->[2]->has_middle_initial, '... got middle initial' );
429 is( $ii->employees->[2]->middle_initial, 'C',
430 '... got the right middle initial value' );
431 is( $ii->employees->[2]->full_name, 'Stevan C. Little, Senior Developer',
432 '... got the right full name' );
433 is( $ii->employees->[2]->title, 'Senior Developer',
434 '... got the right title' );
435 is( $ii->employees->[2]->employer, $ii, '... got the right company' );
436 ok( isweak( $ii->employees->[2]->{employer} ),
437 '... the company is a weak-ref' );
439 isa_ok( $ii->employees->[2]->address, 'Address' );
440 is( $ii->employees->[2]->address->city, 'Madison', '... got the right city' );
441 is( $ii->employees->[2]->address->state, 'CT', '... got the right state' );
446 = Company->new( name => 'Infinity Interactive International' );
447 isa_ok( $new_company, 'Company' );
449 my $ii_employees = $ii->employees;
450 foreach my $employee (@$ii_employees) {
451 is( $employee->employer, $ii, '... has the ii company' );
454 $new_company->employees($ii_employees);
456 foreach my $employee ( @{ $new_company->employees } ) {
457 is( $employee->employer, $new_company,
458 '... has the different company now' );
461 ## check some error conditions for the subtypes
465 Address->new( street => {} ),;
468 '... we die correctly with bad args'
473 Address->new( city => {} ),;
476 '... we die correctly with bad args'
481 Address->new( state => 'British Columbia' ),;
484 '... we die correctly with bad args'
489 Address->new( state => 'Connecticut' ),;
492 '... we live correctly with good args'
497 Address->new( zip_code => 'AF5J6$' ),;
500 '... we die correctly with bad args'
505 Address->new( zip_code => '06443' ),;
508 '... we live correctly with good args'
516 '... we die correctly without good args'
521 Company->new( name => 'Foo' ),;
524 '... we live correctly without good args'
529 Company->new( name => 'Foo', employees => [ Person->new ] ),;
532 '... we die correctly with good args'
537 Company->new( name => 'Foo', employees => [] ),;
540 '... we live correctly with good args'