Commit | Line | Data |
471c4f09 |
1 | |
2 | =pod |
3 | |
4 | =head1 NAME |
5 | |
021b8139 |
6 | Moose::Cookbook::Basics::Recipe4 - Subtypes, and modeling a simple B<Company> class hierarchy |
471c4f09 |
7 | |
8 | =head1 SYNOPSIS |
36c99105 |
9 | |
471c4f09 |
10 | package Address; |
471c4f09 |
11 | use Moose; |
05d9eaf6 |
12 | use Moose::Util::TypeConstraints; |
36c99105 |
13 | |
471c4f09 |
14 | use Locale::US; |
15 | use Regexp::Common 'zip'; |
36c99105 |
16 | |
471c4f09 |
17 | my $STATES = Locale::US->new; |
0b3811a6 |
18 | subtype 'USState' |
471c4f09 |
19 | => as Str |
20 | => where { |
36c99105 |
21 | ( exists $STATES->{code2state}{ uc($_) } |
22 | || exists $STATES->{state2code}{ uc($_) } ); |
23 | }; |
24 | |
0b3811a6 |
25 | subtype 'USZipCode' |
471c4f09 |
26 | => as Value |
27 | => where { |
36c99105 |
28 | /^$RE{zip}{US}{-extended => 'allow'}$/; |
29 | }; |
30 | |
31 | has 'street' => ( is => 'rw', isa => 'Str' ); |
32 | has 'city' => ( is => 'rw', isa => 'Str' ); |
33 | has 'state' => ( is => 'rw', isa => 'USState' ); |
34 | has 'zip_code' => ( is => 'rw', isa => 'USZipCode' ); |
35 | |
f1917f58 |
36 | package Company; |
37 | use Moose; |
38 | use Moose::Util::TypeConstraints; |
36c99105 |
39 | |
40 | has 'name' => ( is => 'rw', isa => 'Str', required => 1 ); |
41 | has 'address' => ( is => 'rw', isa => 'Address' ); |
42 | has 'employees' => ( is => 'rw', isa => 'ArrayRef[Employee]' ); |
43 | |
f1917f58 |
44 | sub BUILD { |
36c99105 |
45 | my ( $self, $params ) = @_; |
4a6b74bd |
46 | if ( @{ $self->employees } ) { |
47 | foreach my $employee ( @{ $self->employees } ) { |
f1917f58 |
48 | $employee->company($self); |
49 | } |
50 | } |
51 | } |
36c99105 |
52 | |
f1917f58 |
53 | after 'employees' => sub { |
36c99105 |
54 | my ( $self, $employees ) = @_; |
4a6b74bd |
55 | if ($employees) { |
36c99105 |
56 | foreach my $employee ( @{$employees} ) { |
f1917f58 |
57 | $employee->company($self); |
36c99105 |
58 | } |
f1917f58 |
59 | } |
36c99105 |
60 | }; |
61 | |
471c4f09 |
62 | package Person; |
471c4f09 |
63 | use Moose; |
36c99105 |
64 | |
65 | has 'first_name' => ( is => 'rw', isa => 'Str', required => 1 ); |
66 | has 'last_name' => ( is => 'rw', isa => 'Str', required => 1 ); |
67 | has 'middle_initial' => ( |
68 | is => 'rw', isa => 'Str', |
69 | predicate => 'has_middle_initial' |
70 | ); |
71 | has 'address' => ( is => 'rw', isa => 'Address' ); |
72 | |
471c4f09 |
73 | sub full_name { |
74 | my $self = shift; |
36c99105 |
75 | return $self->first_name |
76 | . ( |
77 | $self->has_middle_initial |
78 | ? ' ' . $self->middle_initial . '. ' |
79 | : ' ' |
80 | ) . $self->last_name; |
471c4f09 |
81 | } |
36c99105 |
82 | |
471c4f09 |
83 | package Employee; |
36c99105 |
84 | use Moose; |
85 | |
471c4f09 |
86 | extends 'Person'; |
36c99105 |
87 | |
4a6b74bd |
88 | has 'title' => ( is => 'rw', isa => 'Str', required => 1 ); |
89 | has 'employer' => ( is => 'rw', isa => 'Company', weak_ref => 1 ); |
36c99105 |
90 | |
471c4f09 |
91 | override 'full_name' => sub { |
92 | my $self = shift; |
36c99105 |
93 | super() . ', ' . $self->title; |
471c4f09 |
94 | }; |
2f04a0fc |
95 | |
471c4f09 |
96 | =head1 DESCRIPTION |
97 | |
4a6b74bd |
98 | This recipe introduces the C<subtype> sugar function from |
99 | L<Moose::Util::TypeConstraints>. The C<subtype> function lets you |
100 | declaratively create type constraints without building an entire |
101 | class. |
172e0738 |
102 | |
4a6b74bd |
103 | In the recipe we also make use of L<Locale::US> and L<Regexp::Common> |
104 | to build constraints, showing how how constraints can make use of |
105 | existing CPAN tools for data validation. |
36c99105 |
106 | |
4a6b74bd |
107 | Finally, we introduce the C<required> attribute parameter. |
108 | |
109 | The the C<Address> class we define two subtypes. The first uses the |
110 | L<Locale::US> module to check the validaity of a state. It accepts |
111 | either a state abbreviation of full name. |
112 | |
113 | A state will be passed in as a string, so we make our C<USState> type |
114 | a subtype of Moose's builtin C<Str> type. This is done using the C<as> |
115 | sugar. The actual constraint is defined using C<where>. This function |
116 | accepts a single subroutine reference. That subroutine will be called |
117 | with the value to be checked in C<$_> (1). It is expected to return a |
118 | true or false value indicating whether the value is valid for the |
119 | type. |
172e0738 |
120 | |
4a6b74bd |
121 | We can now use the C<USState> type just like Moose's builtin types: |
172e0738 |
122 | |
36c99105 |
123 | has 'state' => ( is => 'rw', isa => 'USState' ); |
172e0738 |
124 | |
4a6b74bd |
125 | When the C<state> attribute is set, the value is checked against the |
126 | C<USState> constraint. If the value is not valid, an exception will be |
127 | thrown. |
172e0738 |
128 | |
4a6b74bd |
129 | The next C<subtype>, C<USZipCode>, uses |
130 | L<Regexp::Common>. L<Regexp::Common> includes a regex for validating |
131 | US zip codes. We use this constraint for the C<zip_code> attribute. |
172e0738 |
132 | |
0b3811a6 |
133 | subtype 'USZipCode' |
172e0738 |
134 | => as Value |
135 | => where { |
36c99105 |
136 | /^$RE{zip}{US}{-extended => 'allow'}$/; |
137 | }; |
172e0738 |
138 | |
4a6b74bd |
139 | Using a subtype instead of requiring a class for each type greatly |
140 | simplifies the code. We don't really need a class for these types, as |
141 | they're just strings, but we do want to ensure that they're valid. |
142 | |
143 | The type constraints we created are reusable. Type constraints are |
144 | stored by name in a global registry. This means that we can refer to |
145 | them in other classes. Because the registry is global, we do recommend |
146 | that you use some sort of pseudo-namespacing in real applications, |
147 | like C<MyApp.Type.USState>. |
148 | |
149 | These two subtypes allow us to define a simple C<Address> class. |
172e0738 |
150 | |
4a6b74bd |
151 | Then we define our C<Company> class, which has an address. As we saw |
152 | in earlier recipes, Moose automatically creates a type constraint for |
153 | each our classes, so we can use that for the C<Company> class's |
154 | C<address> attribute: |
172e0738 |
155 | |
36c99105 |
156 | has 'address' => ( is => 'rw', isa => 'Address' ); |
172e0738 |
157 | |
4a6b74bd |
158 | A company also needs a name: |
172e0738 |
159 | |
36c99105 |
160 | has 'name' => ( is => 'rw', isa => 'Str', required => 1 ); |
172e0738 |
161 | |
4a6b74bd |
162 | This introduces a new attribute parameter, C<required>. If an |
163 | attribute is required, then it must be passed to the class's |
164 | constructor, or an exception will be thrown. It's important to |
165 | understand that a C<required> attribute can still be false or |
166 | C<undef>, if its type constraint allows that. |
f1917f58 |
167 | |
4a6b74bd |
168 | The next attribute, C<employees>, uses a I<parameterized> type |
169 | constraint: |
36c99105 |
170 | |
171 | has 'employees' => ( is => 'rw', isa => 'ArrayRef[Employee]' ); |
07cde929 |
172 | |
4a6b74bd |
173 | This constraint says that C<employees> must be an array reference |
174 | where each element of the array is an C<Employee> object. It's worth |
175 | noting that an I<empty> array reference also satisfies this |
176 | constraint. |
177 | |
178 | Parameterizable type constraints (or "container types), such as |
179 | C<ArrayRef[`a]>, can be made more specific with a type parameter. In |
180 | fact, we can arbitrarily nest these types, producing something like |
181 | C<HashRef[ArrayRef[Int]]>. However, you can also just use the type by |
182 | itself, so C<ArrayRef> is legal. (2) |
183 | |
184 | If you jump down to the definition of the C<Employee> class, you will |
185 | see that it has an C<employer> attribute. |
186 | |
187 | When we set the C<employees> for a C<Company> we want to make sure |
188 | that each of these employee objects refers back to the right |
189 | C<Company> in its C<employer> attribute. |
190 | |
191 | To do that, we need to hook into object construction. Moose lets us do |
192 | this by writing a C<BUILD> method in our class. When your class |
193 | defined a C<BUILD> method, it will be called immediately after an |
194 | object construction, but before the object is returned to the caller |
195 | (3). |
196 | |
197 | The C<Company> class uses the C<BUILD> method to ensure that each |
198 | employee of a company has the proper C<Company> object in its |
199 | C<employer> attribute: |
36c99105 |
200 | |
ad5ed80c |
201 | sub BUILD { |
36c99105 |
202 | my ( $self, $params ) = @_; |
4a6b74bd |
203 | if ( $self->employees ) { |
204 | foreach my $employee ( @{ $self->employees } ) { |
ad5ed80c |
205 | $employee->company($self); |
206 | } |
207 | } |
208 | } |
209 | |
4a6b74bd |
210 | The C<BUILD> method is executed after type constraints are checked, so |
211 | it is safe to assume that C<< $self->employees >> will return an array |
212 | reference, and that the elements of that array will be C<Employee> |
213 | objects. |
214 | |
215 | We also want to make sure that whenever the C<employees> attribute for |
216 | a C<Company> is changed, we also update the C<employer> for each |
217 | employee. |
ad5ed80c |
218 | |
4a6b74bd |
219 | To do this we can use an C<after> modifier: |
ad5ed80c |
220 | |
221 | after 'employees' => sub { |
36c99105 |
222 | my ( $self, $employees ) = @_; |
4a6b74bd |
223 | if ($employees) { |
36c99105 |
224 | foreach my $employee ( @{$employees} ) { |
ad5ed80c |
225 | $employee->company($self); |
36c99105 |
226 | } |
ad5ed80c |
227 | } |
228 | }; |
229 | |
6549b0d1 |
230 | Again, as with the C<BUILD> method, we know that the type constraint |
231 | check has already happened, so we can just check for definedness on the |
ad5ed80c |
232 | C<$employees> argument. |
233 | |
4a6b74bd |
234 | The B<Person> class does have demonstrate anything new. It has several |
235 | C<required> attributes. It also has a C<predicate> method, which we |
236 | first used in L<recipe 3|Moose::Cookbook::Basics::Recipe3>. |
f1917f58 |
237 | |
4a6b74bd |
238 | The only new feature in the C<Employee> class is the C<override> |
239 | method modifier: |
f1917f58 |
240 | |
241 | override 'full_name' => sub { |
242 | my $self = shift; |
36c99105 |
243 | super() . ', ' . $self->title; |
f1917f58 |
244 | }; |
245 | |
4a6b74bd |
246 | This is just a sugary alternative to Perl's built in C<SUPER::> |
247 | feature. However, there is one difference. You cannot pass any |
248 | arguments to C<super>. Instead, Moose ismply passes the same |
249 | parameters that were passed to the method. |
ad5ed80c |
250 | |
4a6b74bd |
251 | A more detailed example of usage can be found in |
252 | F<t/000_recipes/004_recipe.t>. |
ad5ed80c |
253 | |
254 | =head1 CONCLUSION |
255 | |
4a6b74bd |
256 | This recipe was intentionally longer and more complex. It illustrates |
257 | how Moose classes can be used together with type constraints, as well |
258 | as the density of information that you can get out of a small amount |
259 | of typing when using Moose. |
260 | |
261 | This recipe also introduced the C<subtype> function, the C<required> |
262 | attribute, and the C<override> method modifier. |
ad5ed80c |
263 | |
4a6b74bd |
264 | We will revisit type constraints in future recipes, and cover type |
265 | coercion as well. |
e08c54f5 |
266 | |
172e0738 |
267 | =head1 FOOTNOTES |
268 | |
269 | =over 4 |
270 | |
271 | =item (1) |
272 | |
6549b0d1 |
273 | The value being checked is also passed as the first argument to |
4a6b74bd |
274 | the C<where> block, so it can be accessed as C<$_[0]>. |
172e0738 |
275 | |
ad5ed80c |
276 | =item (2) |
277 | |
4a6b74bd |
278 | Note that C<ArrayRef[]> will not work. Moose will not parse this as a |
279 | container type, and instead you will have a new type named |
280 | "ArrayRef[]", which doesn't make any sense. |
281 | |
282 | =item (3) |
283 | |
284 | The C<BUILD> method is actually called by C<< Moose::Object->BUILDALL |
285 | >>, which is called by C<< Moose::Object->new >>. The C<BUILDALL> |
286 | method climbs the object inheritance graph and calls any C<BUILD> |
287 | methods it finds in the correct order. |
ad5ed80c |
288 | |
172e0738 |
289 | =back |
290 | |
8c3d5c88 |
291 | =head1 AUTHORS |
471c4f09 |
292 | |
293 | Stevan Little E<lt>stevan@iinteractive.comE<gt> |
294 | |
8c3d5c88 |
295 | Dave Rolsky E<lt>autarch@urth.orgE<gt> |
296 | |
471c4f09 |
297 | =head1 COPYRIGHT AND LICENSE |
298 | |
2840a3b2 |
299 | Copyright 2006-2009 by Infinity Interactive, Inc. |
471c4f09 |
300 | |
301 | L<http://www.iinteractive.com> |
302 | |
303 | This library is free software; you can redistribute it and/or modify |
304 | it under the same terms as Perl itself. |
305 | |
e08c54f5 |
306 | =cut |