putting the method modifiers in roles back in, we have to maintain backwards compat...
[gitmo/Moose.git] / lib / Moose / Cookbook / Recipe5.pod
1
2 =pod
3
4 =head1 NAME
5
6 Moose::Cookbook::Recipe5 - More subtypes, coercion in a B<Request> class
7
8 =head1 SYNOPSIS
9
10   package Request;
11   use strict;
12   use warnings;
13   use Moose;
14   use Moose::Util::TypeConstraints;
15   
16   use HTTP::Headers  ();
17   use Params::Coerce ();
18   use URI            ();
19   
20   subtype 'Header'
21       => as 'Object'
22       => where { $_->isa('HTTP::Headers') };
23   
24   coerce 'Header'
25       => from 'ArrayRef'
26           => via { HTTP::Headers->new( @{ $_ } ) }
27       => from 'HashRef'
28           => via { HTTP::Headers->new( %{ $_ } ) };
29   
30   subtype 'Uri'
31       => as 'Object'
32       => where { $_->isa('URI') };
33   
34   coerce 'Uri'
35       => from 'Object'
36           => via { $_->isa('URI') 
37                     ? $_ 
38                     : Params::Coerce::coerce( 'URI', $_ ) }
39       => from 'Str'
40           => via { URI->new( $_, 'http' ) };
41   
42   subtype 'Protocol'
43       => as Str
44       => where { /^HTTP\/[0-9]\.[0-9]$/ };
45   
46   has 'base'     => (is => 'rw', isa => 'Uri', coerce  => 1);
47   has 'uri'      => (is => 'rw', isa => 'Uri', coerce  => 1);   
48   has 'method'   => (is => 'rw', isa => 'Str'); 
49   has 'protocol' => (is => 'rw', isa => 'Protocol');            
50   has 'headers'  => (
51       is      => 'rw',
52       isa     => 'Header',
53       coerce  => 1,
54       default => sub { HTTP::Headers->new } 
55   );
56
57 =head1 DESCRIPTION
58
59 This recipe introduces the idea of type coercions, and the C<coerce> 
60 keyword. Coercions can be attached to pre-existing type constraints, 
61 and can be used to transform input of one type, into input of another 
62 type. This can be an extremely powerful tool if used correctly, which 
63 is why, by default, it is off. If you want your accessor to attempt 
64 a coercion, you must specifically ask for it with the B<coerce> option.
65
66 Now, onto the coercions. 
67
68 First we need to create a subtype to attach our coercion too. Here we 
69 create a basic I<Header> subtype, which matches any instance of the 
70 class B<HTTP::Headers>. 
71
72   subtype 'Header'
73       => as 'Object'
74       => where { $_->isa('HTTP::Headers') };
75       
76 The simplest thing from here would be create an accessor declaration 
77 like so:
78
79   has 'headers'  => (
80       is      => 'rw',
81       isa     => 'Header',
82       default => sub { HTTP::Headers->new } 
83   );
84
85 We would then have a self-validating accessor whose default value is 
86 an empty instance of B<HTTP::Headers>. This is nice, but it is not 
87 ideal.
88
89 The constructor for B<HTTP::Headers> accepts a list of key-value pairs
90 representing the fields in an HTTP header. With Perl such a list could 
91 easily be stored into an ARRAY or HASH reference. We would like our 
92 class's interface to be able to accept this list of key-value pairs 
93 in place of the B<HTTP::Headers> instance, and just DWIM. This is where
94 coercion can help. First, lets declare our coercion:
95
96   coerce 'Header'
97       => from 'ArrayRef'
98           => via { HTTP::Headers->new( @{ $_ } ) }
99       => from 'HashRef'
100           => via { HTTP::Headers->new( %{ $_ } ) };
101
102 We first tell it that we are attaching the coercion to the 'Header'
103 subtype. We then give is a set of C<from> clauses which map other 
104 subtypes to coercion routines (through the C<via> keyword). Fairly 
105 simple really, however, this alone does nothing. We have to tell 
106 our attribute declaration to actually use the coercion, like so:
107
108   has 'headers'  => (
109       is      => 'rw',
110       isa     => 'Header',
111       coerce  => 1,
112       default => sub { HTTP::Headers->new } 
113   );
114
115 This will coerce any B<ArrayRef> or B<HashRef> which is passed into 
116 the C<headers> accessor into an instance of B<HTTP::Headers>. So that 
117 the following lines of code are all equivalent:
118
119   $foo->headers(HTTP::Headers->new(bar => 1, baz => 2));
120   $foo->headers([ 'bar', 1, 'baz', 2 ]);  
121   $foo->headers({ bar => 1, baz => 2 });  
122
123 As you can see, careful use of coercions can produce an very open 
124 interface for your class, while still retaining the "safety" of 
125 your type constraint checks.
126
127 Our next coercion takes advantage of the power of CPAN to handle 
128 the details of our coercion. In this particular case it uses the 
129 L<Params::Coerce> module, which fits in rather nicely with L<Moose>.
130
131 Again, we create a simple subtype to represent instance of the 
132 B<URI> class. 
133
134   subtype 'Uri'
135       => as 'Object'
136       => where { $_->isa('URI') };
137
138 Then we add the coercion:
139
140   coerce 'Uri'
141       => from 'Object'
142           => via { $_->isa('URI') 
143                     ? $_ 
144                     : Params::Coerce::coerce( 'URI', $_ ) }
145       => from 'Str'
146           => via { URI->new( $_, 'http' ) };
147
148 The first C<from> clause we introduce is for the 'Object' subtype,
149 an 'Object' is simply, anything which is C<bless>ed. This means 
150 that if the coercion encounters another object, it should use this
151 clause. Now we look at the C<via> block so what it does. First 
152 it checks to see if its a B<URI> instance. Since the coercion 
153 process happens prior to any type constraint checking, it is entirely 
154 possible for this to happen. And if it does happen, we simple want 
155 to pass the instance on through. However, if it is not an instance 
156 of B<URI>, then we need to coerce it. This is where L<Params::Coerce> 
157 can do it's magic, and we can just use it's return value. Simple 
158 really, and much less work since we use a module from CPAN :)
159
160 The second C<from> clause is attached to the 'Str' subtype, and 
161 illustrates how coercions can also be used to handle certain 
162 'default' behaviors. In this coercion, we simple take any string 
163 and pass it into the B<URI> constructor along with the default 
164 'http' scheme type. 
165
166 And of course, our coercions do nothing unless they are told to, 
167 like so:
168
169   has 'base' => (is => 'rw', isa => 'Uri', coerce  => 1);
170   has 'uri'  => (is => 'rw', isa => 'Uri', coerce  => 1);
171
172 As you can see, re-using the coercion allows us to enforce a 
173 consistent and very flexible API across multiple accessors.
174
175 =head1 CONCLUSION
176     
177 This recipe illustrated the power of coercions to build a more 
178 flexible and open API for your accessors, while still retaining 
179 all the safety that comes from using Moose's type constraints. 
180 Using coercions it becomes simple to manage (from a single 
181 location) a consisten API not only across multiple accessors, 
182 but across multiple classes as well. 
183
184 In the next recipe, we will introduce roles, a concept originally 
185 borrowed from Smalltalk, which made it's way into Perl 6, and 
186 now into Moose.
187
188 =head1 AUTHOR
189
190 Stevan Little E<lt>stevan@iinteractive.comE<gt>
191
192 =head1 COPYRIGHT AND LICENSE
193
194 Copyright 2006 by Infinity Interactive, Inc.
195
196 L<http://www.iinteractive.com>
197
198 This library is free software; you can redistribute it and/or modify
199 it under the same terms as Perl itself.
200
201 =cut