more typo fixes
[gitmo/Moose.git] / lib / Moose / Cookbook / Basics / Recipe5.pod
1
2 =pod
3
4 =head1 NAME
5
6 Moose::Cookbook::Basics::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 existing type constraints, and
59 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 it is off by default. If you want your accessor to attempt a
62 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 to. 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 this:
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 HTTP header fields. In Perl, such a list could easily
89 be stored in an ARRAY or HASH reference. We would like our class's
90 interface to be able to accept this list of key-value pairs in place
91 of the B<HTTP::Headers> instance, and just DWIM. This is where
92 coercion can help. First, let's 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 C<Header>
101 subtype. We then give it 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 our
104 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 the
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 a very open
122 interface for your class, while still retaining the "safety" of your
123 type constraint checks.
124
125 Our next coercion takes advantage of the power of CPAN to handle the
126 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 instances of the B<URI>
130 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 C<Object> subtype. An
147 C<Object> is simply any C<bless>ed value. This means that if the
148 coercion encounters another object, it should use this clause. Now we
149 look at the C<via> block.  First it checks to see if the object is a
150 B<URI> instance. Since the coercion process occurs prior to any type
151 constraint checking, it is entirely possible for this to happen, and
152 if it does happen, we simply want to pass the instance on
153 through. However, if it is not an instance of B<URI>, then we need to
154 coerce it. This is where L<Params::Coerce> can do its magic, and we
155 can just use its return value. Simple really, and much less work since
156 we used a module from CPAN. :)
157
158 The second C<from> clause is attached to the C<Str> subtype, and
159 illustrates how coercions can also be used to handle certain "default"
160 behaviors. In this coercion, we simple take any string and pass it to
161 the B<URI> constructor along with the default C<http> scheme type.
162
163 And of course, our coercions do nothing unless they are told to, like
164 so:
165
166   has 'base' => ( is => 'rw', isa => 'Uri', coerce => 1 );
167   has 'uri'  => ( is => 'rw', isa => 'Uri', coerce => 1 );
168
169 As you can see, re-using the coercion allows us to enforce a
170 consistent and very flexible API across multiple accessors.
171
172 =head1 CONCLUSION
173
174 This recipe illustrated the power of coercions to build a more
175 flexible and open API for your accessors, while still retaining all
176 the safety that comes from using Moose's type constraints.  Using
177 coercions it becomes simple to manage (from a single location) a
178 consistent API not only across multiple accessors, but across multiple
179 classes as well.
180
181 In the next recipe, we will introduce roles, a concept originally
182 borrowed from Smalltalk, which made its way into Perl 6, and now into
183 Moose.
184
185 =head1 AUTHOR
186
187 Stevan Little E<lt>stevan@iinteractive.comE<gt>
188
189 =head1 COPYRIGHT AND LICENSE
190
191 Copyright 2006-2009 by Infinity Interactive, Inc.
192
193 L<http://www.iinteractive.com>
194
195 This library is free software; you can redistribute it and/or modify
196 it under the same terms as Perl itself.
197
198 =cut