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