tidy code in pod for consistency, also tidy some text
[gitmo/Moose.git] / lib / Moose / Cookbook / Basics / Recipe5.pod
CommitLineData
471c4f09 1
2=pod
3
4=head1 NAME
5
021b8139 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;
c765b254 13
471c4f09 14 use HTTP::Headers ();
15 use Params::Coerce ();
16 use URI ();
c765b254 17
50ec5055 18 subtype 'Header'
19 => as 'Object'
471c4f09 20 => where { $_->isa('HTTP::Headers') };
c765b254 21
50ec5055 22 coerce 'Header'
23 => from 'ArrayRef'
c765b254 24 => via { HTTP::Headers->new( @{$_} ) }
50ec5055 25 => from 'HashRef'
c765b254 26 => via { HTTP::Headers->new( %{$_} ) };
27
50ec5055 28 subtype 'Uri'
29 => as 'Object'
471c4f09 30 => where { $_->isa('URI') };
c765b254 31
50ec5055 32 coerce 'Uri'
33 => from 'Object'
c765b254 34 => via { $_->isa('URI')
35 ? $_
36 : Params::Coerce::coerce( 'URI', $_ ); }
50ec5055 37 => from 'Str'
471c4f09 38 => via { URI->new( $_, 'http' ) };
c765b254 39
50ec5055 40 subtype 'Protocol'
c765b254 41 => as 'Str'
471c4f09 42 => where { /^HTTP\/[0-9]\.[0-9]$/ };
c765b254 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' );
471c4f09 48 has 'headers' => (
49 is => 'rw',
50 isa => 'Header',
51 coerce => 1,
c765b254 52 default => sub { HTTP::Headers->new }
471c4f09 53 );
54
55=head1 DESCRIPTION
56
c765b254 57This recipe introduces the idea of type coercions, and the C<coerce>
58keyword. Coercions can be attached to existing type constraints, and
59can 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 it is off by default. If you want your accessor to attempt a
62coercion, you must specifically ask for it with the B<coerce> option.
9deed647 63
c765b254 64Now, onto the coercions.
50ec5055 65
c765b254 66First we need to create a subtype to attach our coercion to. Here we
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
c765b254 77 has 'headers' => (
50ec5055 78 is => 'rw',
79 isa => 'Header',
c765b254 80 default => sub { HTTP::Headers->new }
50ec5055 81 );
82
c765b254 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
50ec5055 85ideal.
86
87The constructor for B<HTTP::Headers> accepts a list of key-value pairs
c765b254 88representing the HTTP header fields. In Perl, such a list could easily
89be stored in an ARRAY or HASH reference. We would like our class's
90interface to be able to accept this list of key-value pairs in place
91of 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'
c765b254 96 => via { HTTP::Headers->new( @{$_} ) }
50ec5055 97 => from 'HashRef'
c765b254 98 => via { HTTP::Headers->new( %{$_} ) };
50ec5055 99
100We first tell it that we are attaching the coercion to the 'Header'
c765b254 101subtype. We then give it 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 our
104attribute declaration to actually use the coercion, like so:
50ec5055 105
c765b254 106 has 'headers' => (
50ec5055 107 is => 'rw',
108 isa => 'Header',
109 coerce => 1,
c765b254 110 default => sub { HTTP::Headers->new }
50ec5055 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
c765b254 117 $foo->headers( HTTP::Headers->new( bar => 1, baz => 2 ) );
118 $foo->headers( [ 'bar', 1, 'baz', 2 ] );
119 $foo->headers( { bar => 1, baz => 2 } );
50ec5055 120
c765b254 121As you can see, careful use of coercions can produce a very open
122interface for your class, while still retaining the "safety" of your
123type constraint checks.
50ec5055 124
c765b254 125Our next coercion takes advantage of the power of CPAN to handle the
126details of our coercion. In this particular case it uses the
50ec5055 127L<Params::Coerce> module, which fits in rather nicely with L<Moose>.
128
c765b254 129Again, we create a simple subtype to represent instances of the B<URI>
130class:
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'
c765b254 140 => via { $_->isa('URI')
141 ? $_
142 : Params::Coerce::coerce( 'URI', $_ ); }
50ec5055 143 => from 'Str'
144 => via { URI->new( $_, 'http' ) };
145
c765b254 146The first C<from> clause we introduce is for the 'Object' subtype. An
147'Object' is simply any C<bless>ed value. This means that if the
148coercion encounters another object, it should use this clause. Now we
149look at the C<via> block. First it checks to see if the object is a
150B<URI> instance. Since the coercion process occurs prior to any type
151constraint checking, it is entirely possible for this to happen, and
152if it does happen, we simply want to pass the instance on
153through. However, if it is not an instance of B<URI>, then we need to
154coerce it. This is where L<Params::Coerce> can do its magic, and we
155can just use its return value. Simple really, and much less work since
156we used 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 'default'
160behaviors. In this coercion, we simple take any string and pass it to
161the B<URI> constructor along with the default 'http' scheme type.
162
163And of course, our coercions do nothing unless they are told to, like
164so:
165
166 has 'base' => ( is => 'rw', isa => 'Uri', coerce => 1 );
167 has 'uri' => ( is => 'rw', isa => 'Uri', coerce => 1 );
168
169As you can see, re-using the coercion allows us to enforce a
50ec5055 170consistent and very flexible API across multiple accessors.
171
172=head1 CONCLUSION
12710e29 173
c765b254 174This recipe illustrated the power of coercions to build a more
175flexible and open API for your accessors, while still retaining all
176the safety that comes from using Moose's type constraints. Using
177coercions it becomes simple to manage (from a single location) a
178consistent API not only across multiple accessors, but across multiple
179classes as well.
50ec5055 180
c765b254 181In the next recipe, we will introduce roles, a concept originally
182borrowed from Smalltalk, which made its way into Perl 6, and now into
183Moose.
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
f891e7b7 198=cut