merge trunk to pluggable errors
[gitmo/Moose.git] / lib / Moose / Cookbook / Basics / Recipe5.pod
CommitLineData
471c4f09 1
2=pod
3
4=head1 NAME
5
e606ae5f 6Moose::Cookbook::Basics::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>
6aa9f385 58keyword. Coercions can be attached to existing type constraints,
59and can be used to transform input of one type into input of another
50ec5055 60type. This can be an extremely powerful tool if used correctly, which
6aa9f385 61is why it is off by default. If you want your accessor to attempt
50ec5055 62a coercion, you must specifically ask for it with the B<coerce> option.
9deed647 63
50ec5055 64Now, onto the coercions.
65
6aa9f385 66First we need to create a subtype to attach our coercion to. Here we
50ec5055 67create a basic I<Header> subtype, which matches any instance of the
6aa9f385 68class B<HTTP::Headers>:
50ec5055 69
70 subtype 'Header'
71 => as 'Object'
72 => where { $_->isa('HTTP::Headers') };
6aa9f385 73
74The simplest thing from here would be create an accessor declaration
75like this:
50ec5055 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
6aa9f385 88representing the HTTP header fields. In Perl, such a list could
89easily be stored in an ARRAY or HASH reference. We would like our
50ec5055 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
6aa9f385 92coercion can help. First, let's declare our coercion:
50ec5055 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'
6aa9f385 101subtype. We then give it a set of C<from> clauses which map other
50ec5055 102subtypes to coercion routines (through the C<via> keyword). Fairly
6aa9f385 103simple really; however, this alone does nothing. We have to tell
50ec5055 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
6aa9f385 114the C<headers> accessor into an instance of B<HTTP::Headers>. So the
50ec5055 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
6aa9f385 121As you can see, careful use of coercions can produce a very open
50ec5055 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
6aa9f385 129Again, we create a simple subtype to represent instances of the
130B<URI> class:
50ec5055 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
6aa9f385 146The first C<from> clause we introduce is for the 'Object' subtype. An 'Object'
147is simply any C<bless>ed value. This means that if the coercion encounters
148another object, it should use this clause. Now we look at the C<via> block.
149First it checks to see if the object is a B<URI> instance. Since the coercion
150process occurs prior to any type constraint checking, it is entirely possible
151for this to happen, and if it does happen, we simply want to pass the instance
152on through. However, if it is not an instance of B<URI>, then we need to coerce
153it. This is where L<Params::Coerce> can do its magic, and we can just use its
154return value. Simple really, and much less work since we used a module from CPAN
155:)
50ec5055 156
157The second C<from> clause is attached to the 'Str' subtype, and
158illustrates how coercions can also be used to handle certain
159'default' behaviors. In this coercion, we simple take any string
6aa9f385 160and pass it to the B<URI> constructor along with the default
50ec5055 161'http' scheme type.
162
163And of course, our coercions do nothing unless they are told to,
164like so:
12710e29 165
266fb1a5 166 has 'base' => (is => 'rw', isa => 'Uri', coerce => 1);
167 has 'uri' => (is => 'rw', isa => 'Uri', coerce => 1);
50ec5055 168
169As you can see, re-using the coercion allows us to enforce a
170consistent and very flexible API across multiple accessors.
171
172=head1 CONCLUSION
12710e29 173
50ec5055 174This recipe illustrated the power of coercions to build a more
175flexible and open API for your accessors, while still retaining
176all the safety that comes from using Moose's type constraints.
177Using coercions it becomes simple to manage (from a single
4711f5f7 178location) a consistent API not only across multiple accessors,
50ec5055 179but across multiple classes as well.
180
181In the next recipe, we will introduce roles, a concept originally
182borrowed from Smalltalk, which made it's way into Perl 6, and
183now into Moose.
3824830b 184
471c4f09 185=head1 AUTHOR
186
187Stevan Little E<lt>stevan@iinteractive.comE<gt>
188
189=head1 COPYRIGHT AND LICENSE
190
778db3ac 191Copyright 2006-2008 by Infinity Interactive, Inc.
471c4f09 192
193L<http://www.iinteractive.com>
194
195This library is free software; you can redistribute it and/or modify
196it under the same terms as Perl itself.
197
198=cut