Don't define a coercion directly to a class like URI. Make a subtype
[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
3a4bb3ec 18 subtype 'My.HTTP::Headers' => as class_type('HTTP::Headers');
c765b254 19
3a4bb3ec 20 coerce 'My.HTTP::Headers'
50ec5055 21 => from 'ArrayRef'
c765b254 22 => via { HTTP::Headers->new( @{$_} ) }
50ec5055 23 => from 'HashRef'
c765b254 24 => via { HTTP::Headers->new( %{$_} ) };
25
3a4bb3ec 26 subtype 'My.URI' => as class_type('HTTP::Headers');
c765b254 27
3a4bb3ec 28 coerce 'My.URI'
50ec5055 29 => from 'Object'
c765b254 30 => via { $_->isa('URI')
31 ? $_
32 : Params::Coerce::coerce( 'URI', $_ ); }
50ec5055 33 => from 'Str'
471c4f09 34 => via { URI->new( $_, 'http' ) };
c765b254 35
50ec5055 36 subtype 'Protocol'
c765b254 37 => as 'Str'
471c4f09 38 => where { /^HTTP\/[0-9]\.[0-9]$/ };
c765b254 39
3a4bb3ec 40 has 'base' => ( is => 'rw', isa => 'My.URI', coerce => 1 );
41 has 'uri' => ( is => 'rw', isa => 'My.URI', coerce => 1 );
c765b254 42 has 'method' => ( is => 'rw', isa => 'Str' );
43 has 'protocol' => ( is => 'rw', isa => 'Protocol' );
471c4f09 44 has 'headers' => (
45 is => 'rw',
3a4bb3ec 46 isa => 'My.HTTP::Headers',
471c4f09 47 coerce => 1,
c765b254 48 default => sub { HTTP::Headers->new }
471c4f09 49 );
50
51=head1 DESCRIPTION
52
f07dc78e 53This recipe introduces type coercions, which are defined with the
54C<coerce> sugar function. Coercions are attached to existing type
55constraints, and define a (one-way) transformation from one type to
56another.
57
58This is very powerful, but it's also magical, so you have to
59explicitly ask for an attribute to be coerced. To do this, you must
60set the C<coerce> attribute parameter to a true value.
9deed647 61
f07dc78e 62First, we create the subtype to which we will coerce the other types:
50ec5055 63
3a4bb3ec 64 subtype 'My.HTTP::Headers' => as class_type('HTTP::Headers');
65
66We are creating a subtype rather than using C<HTTP::Headers> as a type
67directly. The reason we do this is coercions are global, and a
68coercion defined for C<HTTP::Headers> in our C<Request> class would
69then be defined for I<all> Moose-using classes in the current Perl
70interpreter. It's a L<best practice|Moose::Manual::BestPractices> to
71avoid this sort of namespace pollution.
50ec5055 72
3a4bb3ec 73The C<class_type> sugar function is simply a shortcut for this:
f07dc78e 74
75 subtype 'HTTP::Headers'
50ec5055 76 => as 'Object'
77 => where { $_->isa('HTTP::Headers') };
6aa9f385 78
f07dc78e 79Internally, Moose creates a type constraint for each Moose-using
80class, but for non-Moose classes, the type must be declared
81explicitly.
82
83We could go ahead and use this new type directly:
50ec5055 84
c765b254 85 has 'headers' => (
50ec5055 86 is => 'rw',
f07dc78e 87 isa => 'HTTP::Headers',
c765b254 88 default => sub { HTTP::Headers->new }
50ec5055 89 );
90
f07dc78e 91This creates a simple attribute which defaults to an empty instance of
92L<HTTP::Headers>.
50ec5055 93
f07dc78e 94The constructor for L<HTTP::Headers> accepts a list of key-value pairs
95representing the HTTP header fields. In Perl, such a list could be
96stored in an ARRAY or HASH reference. We want our C<headers> attribute
97to accept those data structure instead of an B<HTTP::Headers>
98instance, and just do the right thing. This is exactly what coercion
99is for:
50ec5055 100
3a4bb3ec 101 coerce 'My.HTTP::Headers'
50ec5055 102 => from 'ArrayRef'
c765b254 103 => via { HTTP::Headers->new( @{$_} ) }
50ec5055 104 => from 'HashRef'
c765b254 105 => via { HTTP::Headers->new( %{$_} ) };
50ec5055 106
f07dc78e 107The first argument to c<coerce> is the type I<to> which we are
108coercing. Then we give it a set of C<from>/C<via> clauses. The C<from>
109function takes some other type name and C<via> takes a subroutine
110reference which actually does the coercion.
111
112However, defining the coercion doesn't do anything until we tell Moose
113we want a particular attribute to be coerced:
50ec5055 114
c765b254 115 has 'headers' => (
50ec5055 116 is => 'rw',
3a4bb3ec 117 isa => 'My.HTTP::Headers',
50ec5055 118 coerce => 1,
c765b254 119 default => sub { HTTP::Headers->new }
50ec5055 120 );
121
f07dc78e 122Now, if we use an C<ArrayRef> or C<HashRef> to populate C<headers>, it
123will be coerced into a new L<HTTP::Headers> instance. With the
124coercion in place, the following lines of code are all equivalent:
50ec5055 125
c765b254 126 $foo->headers( HTTP::Headers->new( bar => 1, baz => 2 ) );
127 $foo->headers( [ 'bar', 1, 'baz', 2 ] );
128 $foo->headers( { bar => 1, baz => 2 } );
50ec5055 129
c765b254 130As you can see, careful use of coercions can produce a very open
131interface for your class, while still retaining the "safety" of your
f07dc78e 132type constraint checks. (1)
50ec5055 133
f07dc78e 134Our next coercion shows how we can leverage existing CPAN modules to
135help implement coercions. In this case we use L<Params::Coerce>.
50ec5055 136
f07dc78e 137Once again, we need to declare a class type for our non-Moose L<URI>
c765b254 138class:
50ec5055 139
3a4bb3ec 140 subtype 'My.URI' => as class_type('HTTP::Headers');
50ec5055 141
f07dc78e 142Then we define the coercion:
50ec5055 143
3a4bb3ec 144 coerce 'My.URI'
50ec5055 145 => from 'Object'
c765b254 146 => via { $_->isa('URI')
147 ? $_
148 : Params::Coerce::coerce( 'URI', $_ ); }
50ec5055 149 => from 'Str'
150 => via { URI->new( $_, 'http' ) };
151
f07dc78e 152The first coercion takes any object and makes it a C<URI> object. The
153coercion system isn't that smart, and does not check if the object is
154already a L<URI>, so we check for that ourselves. If it's not a L<URI>
155already, we let L<Params::Coerce> do its magic, and we just use its
156return value.
157
158If L<Params::Coerce> didn't return a L<URI> object (for whatever
159reason), Moose would throw a type constraint error.
c765b254 160
f07dc78e 161The other coercion takes a string and converts to a L<URI>. In this
162case, we are using the coercion to apply a default behavior, where a
163string is assumed to be an C<http> URI.
c765b254 164
f07dc78e 165Finally, we need to make sure our attributes enable coercion.
c765b254 166
3a4bb3ec 167 has 'base' => ( is => 'rw', isa => 'My.URI', coerce => 1 );
168 has 'uri' => ( is => 'rw', isa => 'My.URI', coerce => 1 );
c765b254 169
f07dc78e 170Re-using the coercion lets us enforce a consistent API across multiple
171attributes.
50ec5055 172
173=head1 CONCLUSION
12710e29 174
f07dc78e 175This recipe showed the use of coercions to create a more flexible and
176DWIM-y API. Like any powerful magic, we recommend some
177caution. Sometimes it's better to reject a value than just guess at
178how to DWIM.
179
180We also showed the use of the C<class_type> sugar function as a
181shortcut for defining a new subtype of C<Object>
182
183=head1 FOOTNOTES
50ec5055 184
f07dc78e 185=over 4
3824830b 186
f07dc78e 187=item (1)
188
189This particular example could be safer. Really we only want to coerce
190an array with an I<even> number of elements. We could create a new
191C<EvenElementArrayRef> type, and then coerce from that type, as
192opposed to from a plain C<ArrayRef>
193
194=back
195
196=head1 AUTHORS
471c4f09 197
198Stevan Little E<lt>stevan@iinteractive.comE<gt>
199
f07dc78e 200Dave Rolsky E<lt>autarch@urth.orgE<gt>
201
471c4f09 202=head1 COPYRIGHT AND LICENSE
203
2840a3b2 204Copyright 2006-2009 by Infinity Interactive, Inc.
471c4f09 205
206L<http://www.iinteractive.com>
207
208This library is free software; you can redistribute it and/or modify
209it under the same terms as Perl itself.
210
f891e7b7 211=cut