Moose::Cookbook::Recipe3:
[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 };
7c6cacb4 96
471c4f09 97=head1 DESCRIPTION
98
172e0738 99In this recipe we introduce the C<subtype> keyword, and show
100how that can be useful for specifying specific type constraints
101without having to build an entire class to represent them. We
102will also show how this feature can be used to leverage the
103usefulness of CPAN modules. In addition to this, we will also
e08c54f5 104introduce another attribute option.
172e0738 105
e08c54f5 106Let's first get into the C<subtype> features. In the B<Address>
172e0738 107class we have defined two subtypes. The first C<subtype> uses
108the L<Locale::US> module, which provides two hashes which can be
109used to do existence checks for state names and their two letter
110state codes. It is a very simple, and very useful module, and
111perfect to use in a C<subtype> constraint.
112
113 my $STATES = Locale::US->new;
114 subtype USState
115 => as Str
116 => where {
117 (exists $STATES->{code2state}{uc($_)} ||
118 exists $STATES->{state2code}{uc($_)})
119 };
120
121Because we know that states will be passed to us as strings, we
122can make C<USState> a subtype of the built-in type constraint
123C<Str>. This will assure that anything which is a C<USState> will
124also pass as a C<Str>. Next, we create a constraint specializer
125using the C<where> keyword. The value being checked against in
126the C<where> clause can be found in the C<$_> variable (1). Our
127constraint specializer will then look to see if the string given
128is either a state name or a state code. If the string meets this
129criteria, then the constraint will pass, otherwise it will fail.
130We can now use this as we would any built-in constraint, like so:
131
132 has 'state' => (is => 'rw', isa => 'USState');
133
134The C<state> accessor will now check all values against the
135C<USState> constraint, thereby only allowing valid state names or
136state codes to be stored in the C<state> slot.
137
138The next C<subtype>, does pretty much the same thing using the
4711f5f7 139L<Regexp::Common> module, and constraining the C<zip_code> slot.
172e0738 140
141 subtype USZipCode
142 => as Value
143 => where {
144 /^$RE{zip}{US}{-extended => 'allow'}$/
145 };
146
147Using subtypes can save a lot of un-needed abstraction by not
148requiring you to create many small classes for these relatively
149simple values. It also allows you to define these constraints
150and share them among many different classes (avoiding unneeded
151duplication) because type constraints are stored by string in a
152global registry and always accessible to C<has>.
153
e08c54f5 154With these two subtypes and some attributes, we have defined
172e0738 155as much as we need for a basic B<Address> class. Next we define
156a basic B<Company> class, which itself has an address. As we saw in
157earlier recipes, we can use the C<Address> type constraint that
158Moose automatically created for us.
159
160 has 'address' => (is => 'rw', isa => 'Address');
161
162A company also needs a name, so we define that too.
163
164 has 'name' => (is => 'rw', isa => 'Str', required => 1);
165
166Here we introduce another attribute option, the C<required> option.
167This option tells Moose that C<name> is a required parameter in
168the B<Company> constructor, and that the C<name> accessor cannot
169accept an undefined value for the slot. The result is that C<name>
170should always have a value.
171
e08c54f5 172The next attribute option is not actually new, but a new variant
ad5ed80c 173of options we have already introduced.
174
175 has 'employees' => (is => 'rw', isa => subtype ArrayRef => where {
176 (blessed($_) && $_->isa('Employee') || return) for @$_; 1
177 });
e08c54f5 178
ad5ed80c 179Here, instead of passing a string to the C<isa> option, we are passing
4711f5f7 180an anonymous subtype of the C<ArrayRef> type constraint. This subtype
ad5ed80c 181basically checks that all the values in the ARRAY ref are instance of
182the B<Employee> class.
183
184Now this will assure that our employee's will all be of the correct
185type, however, the B<Employee> object (which we will see in a moment)
186also maintains a reference back to it's associated B<Company>. In order
187to maintain this relationship (and preserve the referential integrity
188of our objects), we need to do some processing of the employees over
189and above that of the type constraint check. This is accomplished in
190two places. First we need to be sure that any employees array passed
191to the constructor is properly initialized. For this we can use the
192C<BUILD> method (2).
193
194 sub BUILD {
195 my ($self, $params) = @_;
196 if ($params->{employees}) {
197 foreach my $employee (@{$params->{employees}}) {
198 $employee->company($self);
199 }
200 }
201 }
202
4711f5f7 203The C<BUILD> method will have run after the initial type constraint
ad5ed80c 204check, so we can do just a basic existence check on the C<employees>
205param here, and assume that if it does exist, it is both an ARRAY ref
206and full of I<only> instances of B<Employee>.
207
208The next place we need to address is the C<employees> read/write
209accessor (see the C<employees> attribute declaration above). This
210accessor will properly check the type constraint, but we need to add
e08c54f5 211some additional behavior. For this we use an C<after> method modifier
ad5ed80c 212like so:
213
214 after 'employees' => sub {
215 my ($self, $employees) = @_;
216 if (defined $employees) {
217 foreach my $employee (@{$employees}) {
218 $employee->company($self);
219 }
220 }
221 };
222
223Again, as with the C<BUILD> method, we know that the type constraint
224check has already happened, so we can just check for defined-ness on the
225C<$employees> argument.
226
227At this point, our B<Company> class is complete. Next comes our B<Person>
e08c54f5 228class and its subclass the previously mentioned B<Employee> class.
ad5ed80c 229
230The B<Person> class should be obvious to you at this point. It has a few
4711f5f7 231C<required> attributes, and the C<middle_initial> slot has an additional
ad5ed80c 232C<predicate> method (which we saw in the previous recipe with the
233B<BinaryTree> class).
234
235Next the B<Employee> class, this too should be pretty obvious at this
4711f5f7 236point. It requires a C<title>, and maintains a weakened reference to a
ad5ed80c 237B<Company> instance. The only new item, which we have seen before in
238examples, but never in the recipe itself, is the C<override> method
239modifier.
e08c54f5 240
ad5ed80c 241 override 'full_name' => sub {
242 my $self = shift;
243 super() . ', ' . $self->title
244 };
245
e08c54f5 246This just tells Moose that I am intentionally overriding the superclass
ad5ed80c 247C<full_name> method here, and adding the value of the C<title> slot at
248the end of the employee's full name.
249
e08c54f5 250And that's about it.
ad5ed80c 251
252Once again, as with all the other recipes, you can go about using
253these classes like any other Perl 5 class. A more detailed example of
db1ab48d 254usage can be found in F<t/004_recipe.t>.
ad5ed80c 255
256=head1 CONCLUSION
257
258This recipe was intentionally longer and more complex to illustrate both
259how easily Moose classes can interact (using class type constraints, etc.)
260and the shear density of information and behaviors which Moose can pack
261into a relatively small amount of typing. Ponder for a moment how much
262more code a non-Moose plain old Perl 5 version of this recipe would have
263been (including all the type constraint checks, weak references, etc).
264
265And of course, this recipe also introduced the C<subtype> keyword, and
e08c54f5 266its usefulness within the Moose toolkit. In the next recipe we will
ad5ed80c 267focus more on subtypes, and introduce the idea of type coercion as well.
e08c54f5 268
172e0738 269=head1 FOOTNOTES
270
271=over 4
272
273=item (1)
274
275The value being checked is also passed as the first argument to
276the C<where> block as well, so it can also be accessed as C<$_[0]>
277as well.
278
ad5ed80c 279=item (2)
280
281The C<BUILD> method is called by C<Moose::Object::BUILDALL>, which is
282called by C<Moose::Object::new>. C<BUILDALL> will climb the object
4711f5f7 283inheritance graph and call the appropriate C<BUILD> methods in the
ad5ed80c 284correct order.
285
172e0738 286=back
287
471c4f09 288=head1 AUTHOR
289
290Stevan Little E<lt>stevan@iinteractive.comE<gt>
291
292=head1 COPYRIGHT AND LICENSE
293
b77fdbed 294Copyright 2006, 2007 by Infinity Interactive, Inc.
471c4f09 295
296L<http://www.iinteractive.com>
297
298This library is free software; you can redistribute it and/or modify
299it under the same terms as Perl itself.
300
e08c54f5 301=cut