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