DEATH TO ALL zionist ELLIPSES
[gitmo/Moose.git] / lib / Moose / Cookbook / Basics / Recipe5.pod
CommitLineData
471c4f09 1
2=pod
3
5547fba7 4=begin testing-SETUP
c79239a2 5
6BEGIN {
7 eval 'use HTTP::Headers; use Params::Coerce; use URI;';
8 if ($@) {
9 diag 'HTTP::Headers, Params::Coerce & URI required for this test';
10 ok(1);
11 exit 0;
12 }
13}
14
5547fba7 15=end testing-SETUP
c79239a2 16
471c4f09 17=head1 NAME
18
021b8139 19Moose::Cookbook::Basics::Recipe5 - More subtypes, coercion in a B<Request> class
471c4f09 20
21=head1 SYNOPSIS
22
23 package Request;
471c4f09 24 use Moose;
05d9eaf6 25 use Moose::Util::TypeConstraints;
c765b254 26
471c4f09 27 use HTTP::Headers ();
28 use Params::Coerce ();
29 use URI ();
c765b254 30
66b58567 31 subtype 'My::Types::HTTP::Headers' => as class_type('HTTP::Headers');
c765b254 32
66b58567 33 coerce 'My::Types::HTTP::Headers'
50ec5055 34 => from 'ArrayRef'
c765b254 35 => via { HTTP::Headers->new( @{$_} ) }
50ec5055 36 => from 'HashRef'
c765b254 37 => via { HTTP::Headers->new( %{$_} ) };
38
66b58567 39 subtype 'My::Types::URI' => as class_type('URI');
c765b254 40
66b58567 41 coerce 'My::Types::URI'
50ec5055 42 => from 'Object'
c765b254 43 => via { $_->isa('URI')
44 ? $_
45 : Params::Coerce::coerce( 'URI', $_ ); }
50ec5055 46 => from 'Str'
471c4f09 47 => via { URI->new( $_, 'http' ) };
c765b254 48
50ec5055 49 subtype 'Protocol'
c765b254 50 => as 'Str'
471c4f09 51 => where { /^HTTP\/[0-9]\.[0-9]$/ };
c765b254 52
66b58567 53 has 'base' => ( is => 'rw', isa => 'My::Types::URI', coerce => 1 );
54 has 'uri' => ( is => 'rw', isa => 'My::Types::URI', coerce => 1 );
c765b254 55 has 'method' => ( is => 'rw', isa => 'Str' );
56 has 'protocol' => ( is => 'rw', isa => 'Protocol' );
471c4f09 57 has 'headers' => (
58 is => 'rw',
66b58567 59 isa => 'My::Types::HTTP::Headers',
471c4f09 60 coerce => 1,
c765b254 61 default => sub { HTTP::Headers->new }
471c4f09 62 );
63
64=head1 DESCRIPTION
65
f07dc78e 66This recipe introduces type coercions, which are defined with the
67C<coerce> sugar function. Coercions are attached to existing type
68constraints, and define a (one-way) transformation from one type to
69another.
70
71This is very powerful, but it's also magical, so you have to
72explicitly ask for an attribute to be coerced. To do this, you must
16fb3624 73set the C<coerce> attribute option to a true value.
9deed647 74
f07dc78e 75First, we create the subtype to which we will coerce the other types:
50ec5055 76
66b58567 77 subtype 'My::Types::HTTP::Headers' => as class_type('HTTP::Headers');
3a4bb3ec 78
79We are creating a subtype rather than using C<HTTP::Headers> as a type
80directly. The reason we do this is coercions are global, and a
81coercion defined for C<HTTP::Headers> in our C<Request> class would
82then be defined for I<all> Moose-using classes in the current Perl
83interpreter. It's a L<best practice|Moose::Manual::BestPractices> to
84avoid this sort of namespace pollution.
50ec5055 85
3a4bb3ec 86The C<class_type> sugar function is simply a shortcut for this:
f07dc78e 87
88 subtype 'HTTP::Headers'
50ec5055 89 => as 'Object'
90 => where { $_->isa('HTTP::Headers') };
6aa9f385 91
f07dc78e 92Internally, Moose creates a type constraint for each Moose-using
93class, but for non-Moose classes, the type must be declared
94explicitly.
95
96We could go ahead and use this new type directly:
50ec5055 97
c765b254 98 has 'headers' => (
50ec5055 99 is => 'rw',
f07dc78e 100 isa => 'HTTP::Headers',
c765b254 101 default => sub { HTTP::Headers->new }
50ec5055 102 );
103
f07dc78e 104This creates a simple attribute which defaults to an empty instance of
105L<HTTP::Headers>.
50ec5055 106
f07dc78e 107The constructor for L<HTTP::Headers> accepts a list of key-value pairs
108representing the HTTP header fields. In Perl, such a list could be
109stored in an ARRAY or HASH reference. We want our C<headers> attribute
110to accept those data structure instead of an B<HTTP::Headers>
111instance, and just do the right thing. This is exactly what coercion
112is for:
50ec5055 113
66b58567 114 coerce 'My::Types::HTTP::Headers'
50ec5055 115 => from 'ArrayRef'
c765b254 116 => via { HTTP::Headers->new( @{$_} ) }
50ec5055 117 => from 'HashRef'
c765b254 118 => via { HTTP::Headers->new( %{$_} ) };
50ec5055 119
e39d2b6b 120The first argument to C<coerce> is the type I<to> which we are
f07dc78e 121coercing. Then we give it a set of C<from>/C<via> clauses. The C<from>
122function takes some other type name and C<via> takes a subroutine
123reference which actually does the coercion.
124
125However, defining the coercion doesn't do anything until we tell Moose
126we want a particular attribute to be coerced:
50ec5055 127
c765b254 128 has 'headers' => (
50ec5055 129 is => 'rw',
66b58567 130 isa => 'My::Types::HTTP::Headers',
50ec5055 131 coerce => 1,
c765b254 132 default => sub { HTTP::Headers->new }
50ec5055 133 );
134
f07dc78e 135Now, if we use an C<ArrayRef> or C<HashRef> to populate C<headers>, it
136will be coerced into a new L<HTTP::Headers> instance. With the
137coercion in place, the following lines of code are all equivalent:
50ec5055 138
c765b254 139 $foo->headers( HTTP::Headers->new( bar => 1, baz => 2 ) );
140 $foo->headers( [ 'bar', 1, 'baz', 2 ] );
141 $foo->headers( { bar => 1, baz => 2 } );
50ec5055 142
c765b254 143As you can see, careful use of coercions can produce a very open
144interface for your class, while still retaining the "safety" of your
f07dc78e 145type constraint checks. (1)
50ec5055 146
f07dc78e 147Our next coercion shows how we can leverage existing CPAN modules to
148help implement coercions. In this case we use L<Params::Coerce>.
50ec5055 149
f07dc78e 150Once again, we need to declare a class type for our non-Moose L<URI>
c765b254 151class:
50ec5055 152
66b58567 153 subtype 'My::Types::URI' => as class_type('URI');
50ec5055 154
f07dc78e 155Then we define the coercion:
50ec5055 156
66b58567 157 coerce 'My::Types::URI'
50ec5055 158 => from 'Object'
c765b254 159 => via { $_->isa('URI')
160 ? $_
161 : Params::Coerce::coerce( 'URI', $_ ); }
50ec5055 162 => from 'Str'
163 => via { URI->new( $_, 'http' ) };
164
f07dc78e 165The first coercion takes any object and makes it a C<URI> object. The
166coercion system isn't that smart, and does not check if the object is
167already a L<URI>, so we check for that ourselves. If it's not a L<URI>
168already, we let L<Params::Coerce> do its magic, and we just use its
169return value.
170
171If L<Params::Coerce> didn't return a L<URI> object (for whatever
172reason), Moose would throw a type constraint error.
c765b254 173
f07dc78e 174The other coercion takes a string and converts to a L<URI>. In this
175case, we are using the coercion to apply a default behavior, where a
176string is assumed to be an C<http> URI.
c765b254 177
f07dc78e 178Finally, we need to make sure our attributes enable coercion.
c765b254 179
66b58567 180 has 'base' => ( is => 'rw', isa => 'My::Types::URI', coerce => 1 );
181 has 'uri' => ( is => 'rw', isa => 'My::Types::URI', coerce => 1 );
c765b254 182
f07dc78e 183Re-using the coercion lets us enforce a consistent API across multiple
184attributes.
50ec5055 185
186=head1 CONCLUSION
12710e29 187
f07dc78e 188This recipe showed the use of coercions to create a more flexible and
189DWIM-y API. Like any powerful magic, we recommend some
190caution. Sometimes it's better to reject a value than just guess at
191how to DWIM.
192
193We also showed the use of the C<class_type> sugar function as a
194shortcut for defining a new subtype of C<Object>
195
196=head1 FOOTNOTES
50ec5055 197
f07dc78e 198=over 4
3824830b 199
f07dc78e 200=item (1)
201
202This particular example could be safer. Really we only want to coerce
203an array with an I<even> number of elements. We could create a new
204C<EvenElementArrayRef> type, and then coerce from that type, as
205opposed to from a plain C<ArrayRef>
206
207=back
208
209=head1 AUTHORS
471c4f09 210
211Stevan Little E<lt>stevan@iinteractive.comE<gt>
212
f07dc78e 213Dave Rolsky E<lt>autarch@urth.orgE<gt>
214
471c4f09 215=head1 COPYRIGHT AND LICENSE
216
2840a3b2 217Copyright 2006-2009 by Infinity Interactive, Inc.
471c4f09 218
219L<http://www.iinteractive.com>
220
221This library is free software; you can redistribute it and/or modify
222it under the same terms as Perl itself.
223
c79239a2 224=begin testing
225
226my $r = Request->new;
227isa_ok( $r, 'Request' );
228
229{
230 my $header = $r->headers;
231 isa_ok( $header, 'HTTP::Headers' );
232
233 is( $r->headers->content_type, '',
1808c2da 234 'got no content type in the header' );
c79239a2 235
236 $r->headers( { content_type => 'text/plain' } );
237
238 my $header2 = $r->headers;
239 isa_ok( $header2, 'HTTP::Headers' );
1808c2da 240 isnt( $header, $header2, 'created a new HTTP::Header object' );
c79239a2 241
242 is( $header2->content_type, 'text/plain',
1808c2da 243 'got the right content type in the header' );
c79239a2 244
245 $r->headers( [ content_type => 'text/html' ] );
246
247 my $header3 = $r->headers;
248 isa_ok( $header3, 'HTTP::Headers' );
1808c2da 249 isnt( $header2, $header3, 'created a new HTTP::Header object' );
c79239a2 250
251 is( $header3->content_type, 'text/html',
1808c2da 252 'got the right content type in the header' );
c79239a2 253
254 $r->headers( HTTP::Headers->new( content_type => 'application/pdf' ) );
255
256 my $header4 = $r->headers;
257 isa_ok( $header4, 'HTTP::Headers' );
1808c2da 258 isnt( $header3, $header4, 'created a new HTTP::Header object' );
c79239a2 259
260 is( $header4->content_type, 'application/pdf',
1808c2da 261 'got the right content type in the header' );
c79239a2 262
263 dies_ok {
264 $r->headers('Foo');
265 }
1808c2da 266 'dies when it gets bad params';
c79239a2 267}
268
269{
1808c2da 270 is( $r->protocol, undef, 'got nothing by default' );
c79239a2 271
272 lives_ok {
273 $r->protocol('HTTP/1.0');
274 }
1808c2da 275 'set the protocol correctly';
276 is( $r->protocol, 'HTTP/1.0', 'got nothing by default' );
c79239a2 277
278 dies_ok {
279 $r->protocol('http/1.0');
280 }
1808c2da 281 'the protocol died with bar params correctly';
c79239a2 282}
283
bd538e29 284{
285 $r->base('http://localhost/');
286 isa_ok( $r->base, 'URI' );
287
288 $r->uri('http://localhost/');
289 isa_ok( $r->uri, 'URI' );
290}
291
c79239a2 292=end testing
293
f891e7b7 294=cut