TypeConstraint::Utils,.. now with find_or_create_type_constraint goodness
[gitmo/Moose.git] / lib / Moose / Cookbook / Recipe4.pod
CommitLineData
471c4f09 1
2=pod
3
4=head1 NAME
5
3824830b 6Moose::Cookbook::Recipe4 - Subtypes, and modeling a simple B<Company> class hierarchy
471c4f09 7
8=head1 SYNOPSIS
9
10 package Address;
471c4f09 11 use Moose;
05d9eaf6 12 use Moose::Util::TypeConstraints;
471c4f09 13
14 use Locale::US;
15 use Regexp::Common 'zip';
16
17 my $STATES = Locale::US->new;
18
19 subtype USState
20 => as Str
21 => where {
172e0738 22 (exists $STATES->{code2state}{uc($_)} ||
23 exists $STATES->{state2code}{uc($_)})
471c4f09 24 };
25
26 subtype USZipCode
27 => as Value
28 => where {
29 /^$RE{zip}{US}{-extended => 'allow'}$/
30 };
31
32 has 'street' => (is => 'rw', isa => 'Str');
33 has 'city' => (is => 'rw', isa => 'Str');
34 has 'state' => (is => 'rw', isa => 'USState');
35 has 'zip_code' => (is => 'rw', isa => 'USZipCode');
36
37 package Company;
471c4f09 38 use Moose;
05d9eaf6 39 use Moose::Util::TypeConstraints;
471c4f09 40
7c6cacb4 41 has 'name' => (is => 'rw', isa => 'Str', required => 1);
471c4f09 42 has 'address' => (is => 'rw', isa => 'Address');
43 has 'employees' => (is => 'rw', isa => subtype ArrayRef => where {
ad5ed80c 44 (blessed($_) && $_->isa('Employee') || return) for @$_; 1
471c4f09 45 });
46
47 sub BUILD {
48 my ($self, $params) = @_;
49 if ($params->{employees}) {
50 foreach my $employee (@{$params->{employees}}) {
51 $employee->company($self);
52 }
53 }
54 }
55
ad5ed80c 56 after 'employees' => sub {
57 my ($self, $employees) = @_;
58 if (defined $employees) {
59 foreach my $employee (@{$employees}) {
60 $employee->company($self);
61 }
62 }
63 };
471c4f09 64
65 package Person;
471c4f09 66 use Moose;
67
7c6cacb4 68 has 'first_name' => (is => 'rw', isa => 'Str', required => 1);
69 has 'last_name' => (is => 'rw', isa => 'Str', required => 1);
172e0738 70 has 'middle_initial' => (is => 'rw', isa => 'Str',
71 predicate => 'has_middle_initial');
471c4f09 72 has 'address' => (is => 'rw', isa => 'Address');
73
74 sub full_name {
75 my $self = shift;
76 return $self->first_name .
172e0738 77 ($self->has_middle_initial ?
78 ' ' . $self->middle_initial . '. '
79 :
80 ' ') .
471c4f09 81 $self->last_name;
82 }
83
84 package Employee;
471c4f09 85 use Moose;
86
87 extends 'Person';
88
7c6cacb4 89 has 'title' => (is => 'rw', isa => 'Str', required => 1);
471c4f09 90 has 'company' => (is => 'rw', isa => 'Company', weak_ref => 1);
91
92 override 'full_name' => sub {
93 my $self = shift;
94 super() . ', ' . $self->title
95 };
2f04a0fc 96
471c4f09 97=head1 DESCRIPTION
98
172e0738 99In this recipe we introduce the C<subtype> keyword, and show
2f04a0fc 100how it can be useful for specifying type constraints
101without building an entire class to represent them. We
172e0738 102will also show how this feature can be used to leverage the
2f04a0fc 103usefulness of CPAN modules. In addition to this, we will
e08c54f5 104introduce another attribute option.
172e0738 105
2f04a0fc 106Let's first look at the C<subtype> feature. In the B<Address> class we have
107defined two subtypes. The first C<subtype> uses the L<Locale::US> module, which
108provides two hashes which can be used to perform existential checks for state
109names and their two letter state codes. It is a very simple and very useful
110module, and perfect for use in a C<subtype> constraint.
172e0738 111
112 my $STATES = Locale::US->new;
113 subtype USState
114 => as Str
115 => where {
116 (exists $STATES->{code2state}{uc($_)} ||
117 exists $STATES->{state2code}{uc($_)})
118 };
119
120Because we know that states will be passed to us as strings, we
121can make C<USState> a subtype of the built-in type constraint
2f04a0fc 122C<Str>. This will ensure that anything which is a C<USState> will
172e0738 123also pass as a C<Str>. Next, we create a constraint specializer
124using the C<where> keyword. The value being checked against in
125the C<where> clause can be found in the C<$_> variable (1). Our
2f04a0fc 126constraint specializer will then check whether the given string
172e0738 127is either a state name or a state code. If the string meets this
128criteria, then the constraint will pass, otherwise it will fail.
129We can now use this as we would any built-in constraint, like so:
130
131 has 'state' => (is => 'rw', isa => 'USState');
132
133The C<state> accessor will now check all values against the
134C<USState> constraint, thereby only allowing valid state names or
135state codes to be stored in the C<state> slot.
136
2f04a0fc 137The next C<subtype> does pretty much the same thing using the L<Regexp::Common>
138module, and is used as the constraint for the C<zip_code> slot.
172e0738 139
140 subtype USZipCode
141 => as Value
142 => where {
143 /^$RE{zip}{US}{-extended => 'allow'}$/
144 };
145
2f04a0fc 146Using subtypes can save a lot of unnecessary abstraction by not requiring you to
147create many small classes for these relatively simple values. They also allow
148you to reuse the same constraints in a number of classes (thereby avoiding
149duplication), since all type constraints are stored in a global registry and
150always accessible to C<has>.
172e0738 151
e08c54f5 152With these two subtypes and some attributes, we have defined
2f04a0fc 153as much as we need for a basic B<Address> class. Next, we define
172e0738 154a basic B<Company> class, which itself has an address. As we saw in
155earlier recipes, we can use the C<Address> type constraint that
2f04a0fc 156Moose automatically created for us:
172e0738 157
158 has 'address' => (is => 'rw', isa => 'Address');
159
2f04a0fc 160A company also needs a name, so we define that as well:
172e0738 161
162 has 'name' => (is => 'rw', isa => 'Str', required => 1);
163
164Here we introduce another attribute option, the C<required> option.
165This option tells Moose that C<name> is a required parameter in
166the B<Company> constructor, and that the C<name> accessor cannot
167accept an undefined value for the slot. The result is that C<name>
2f04a0fc 168will always have a value.
172e0738 169
e08c54f5 170The next attribute option is not actually new, but a new variant
2f04a0fc 171of options we have already introduced:
ad5ed80c 172
173 has 'employees' => (is => 'rw', isa => subtype ArrayRef => where {
174 (blessed($_) && $_->isa('Employee') || return) for @$_; 1
175 });
e08c54f5 176
ad5ed80c 177Here, instead of passing a string to the C<isa> option, we are passing
4711f5f7 178an anonymous subtype of the C<ArrayRef> type constraint. This subtype
2f04a0fc 179basically checks that all the values in the ARRAY ref are instances of
ad5ed80c 180the B<Employee> class.
181
2f04a0fc 182This will ensure that our employees will all be of the correct type. However,
183the B<Employee> object (which we will see in a moment) also maintains a
184reference to its associated B<Company>. In order to maintain this relationship
185(and preserve the referential integrity of our objects), we need to perform some
186processing of the employees over and above that of the type constraint check.
187This is accomplished in two places. First we need to be sure that any employees
188array passed to the constructor is properly initialized. For this we can use the
189C<BUILD> method (2):
ad5ed80c 190
191 sub BUILD {
192 my ($self, $params) = @_;
193 if ($params->{employees}) {
194 foreach my $employee (@{$params->{employees}}) {
195 $employee->company($self);
196 }
197 }
198 }
199
2f04a0fc 200The C<BUILD> method will be executed after the initial type constraint
201check, so we can simply perform a basic existential check on the C<employees>
ad5ed80c 202param here, and assume that if it does exist, it is both an ARRAY ref
2f04a0fc 203and contains I<only> instances of B<Employee>.
ad5ed80c 204
2f04a0fc 205The next aspect we need to address is the C<employees> read/write
ad5ed80c 206accessor (see the C<employees> attribute declaration above). This
2f04a0fc 207accessor will correctly check the type constraint, but we need to extend it
208with some additional processing. For this we use an C<after> method modifier,
ad5ed80c 209like so:
210
211 after 'employees' => sub {
212 my ($self, $employees) = @_;
213 if (defined $employees) {
214 foreach my $employee (@{$employees}) {
215 $employee->company($self);
216 }
217 }
218 };
219
220Again, as with the C<BUILD> method, we know that the type constraint
221check has already happened, so we can just check for defined-ness on the
222C<$employees> argument.
223
224At this point, our B<Company> class is complete. Next comes our B<Person>
2f04a0fc 225class and its subclass, the previously mentioned B<Employee> class.
ad5ed80c 226
227The B<Person> class should be obvious to you at this point. It has a few
4711f5f7 228C<required> attributes, and the C<middle_initial> slot has an additional
ad5ed80c 229C<predicate> method (which we saw in the previous recipe with the
230B<BinaryTree> class).
231
2f04a0fc 232Next, the B<Employee> class, which should also be pretty obvious at this
4711f5f7 233point. It requires a C<title>, and maintains a weakened reference to a
ad5ed80c 234B<Company> instance. The only new item, which we have seen before in
235examples, but never in the recipe itself, is the C<override> method
2f04a0fc 236modifier:
e08c54f5 237
ad5ed80c 238 override 'full_name' => sub {
239 my $self = shift;
240 super() . ', ' . $self->title
241 };
242
e08c54f5 243This just tells Moose that I am intentionally overriding the superclass
ad5ed80c 244C<full_name> method here, and adding the value of the C<title> slot at
245the end of the employee's full name.
246
e08c54f5 247And that's about it.
ad5ed80c 248
249Once again, as with all the other recipes, you can go about using
250these classes like any other Perl 5 class. A more detailed example of
db1ab48d 251usage can be found in F<t/004_recipe.t>.
ad5ed80c 252
253=head1 CONCLUSION
254
255This recipe was intentionally longer and more complex to illustrate both
256how easily Moose classes can interact (using class type constraints, etc.)
2f04a0fc 257and the sheer density of information and behaviors which Moose can pack
ad5ed80c 258into a relatively small amount of typing. Ponder for a moment how much
259more code a non-Moose plain old Perl 5 version of this recipe would have
2f04a0fc 260been (including all the type constraint checks, weak references, and so on).
ad5ed80c 261
262And of course, this recipe also introduced the C<subtype> keyword, and
e08c54f5 263its usefulness within the Moose toolkit. In the next recipe we will
ad5ed80c 264focus more on subtypes, and introduce the idea of type coercion as well.
e08c54f5 265
172e0738 266=head1 FOOTNOTES
267
268=over 4
269
270=item (1)
271
272The value being checked is also passed as the first argument to
273the C<where> block as well, so it can also be accessed as C<$_[0]>
274as well.
275
ad5ed80c 276=item (2)
277
278The C<BUILD> method is called by C<Moose::Object::BUILDALL>, which is
279called by C<Moose::Object::new>. C<BUILDALL> will climb the object
4711f5f7 280inheritance graph and call the appropriate C<BUILD> methods in the
ad5ed80c 281correct order.
282
172e0738 283=back
284
471c4f09 285=head1 AUTHOR
286
287Stevan Little E<lt>stevan@iinteractive.comE<gt>
288
289=head1 COPYRIGHT AND LICENSE
290
b77fdbed 291Copyright 2006, 2007 by Infinity Interactive, Inc.
471c4f09 292
293L<http://www.iinteractive.com>
294
295This library is free software; you can redistribute it and/or modify
296it under the same terms as Perl itself.
297
e08c54f5 298=cut