use Params::Coerce ();
use URI ();
- subtype Header
- => as Object
+ subtype 'Header'
+ => as 'Object'
=> where { $_->isa('HTTP::Headers') };
- coerce Header
- => from ArrayRef
+ coerce 'Header'
+ => from 'ArrayRef'
=> via { HTTP::Headers->new( @{ $_ } ) }
- => from HashRef
+ => from 'HashRef'
=> via { HTTP::Headers->new( %{ $_ } ) };
- subtype Uri
- => as Object
+ subtype 'Uri'
+ => as 'Object'
=> where { $_->isa('URI') };
- coerce Uri
- => from Object
- => via { $_->isa('URI') ? $_ : Params::Coerce::coerce( 'URI', $_ ) }
- => from Str
+ coerce 'Uri'
+ => from 'Object'
+ => via { $_->isa('URI')
+ ? $_
+ : Params::Coerce::coerce( 'URI', $_ ) }
+ => from 'Str'
=> via { URI->new( $_, 'http' ) };
- subtype Protocol
+ subtype 'Protocol'
=> as Str
=> where { /^HTTP\/[0-9]\.[0-9]$/ };
has 'base' => (is => 'rw', isa => 'Uri', coerce => 1);
- has 'url' => (is => 'rw', isa => 'Uri', coerce => 1);
+ has 'uri' => (is => 'rw', isa => 'Uri', coerce => 1);
has 'method' => (is => 'rw', isa => 'Str');
has 'protocol' => (is => 'rw', isa => 'Protocol');
has 'headers' => (
=head1 DESCRIPTION
-Coming Soon.
+This recipe introduces the idea of type coercions, and the C<coerce>
+keyword. Coercions can be attached to pre-existing type constraints,
+and can be used to transform input of one type, into input of another
+type. This can be an extremely powerful tool if used correctly, which
+is why, by default, it is off. If you want your accessor to attempt
+a coercion, you must specifically ask for it with the B<coerce> option.
-(the other 4 recipes kinda burned me out a bit)
+Now, onto the coercions.
+
+First we need to create a subtype to attach our coercion too. Here we
+create a basic I<Header> subtype, which matches any instance of the
+class B<HTTP::Headers>.
+
+ subtype 'Header'
+ => as 'Object'
+ => where { $_->isa('HTTP::Headers') };
+
+The simplest thing from here would be create an accessor declaration
+like so:
+
+ has 'headers' => (
+ is => 'rw',
+ isa => 'Header',
+ default => sub { HTTP::Headers->new }
+ );
+
+We would then have a self-validating accessor whose default value is
+an empty instance of B<HTTP::Headers>. This is nice, but it is not
+ideal.
+
+The constructor for B<HTTP::Headers> accepts a list of key-value pairs
+representing the fields in an HTTP header. With Perl such a list could
+easily be stored into an ARRAY or HASH reference. We would like our
+class's interface to be able to accept this list of key-value pairs
+in place of the B<HTTP::Headers> instance, and just DWIM. This is where
+coercion can help. First, lets declare our coercion:
+
+ coerce 'Header'
+ => from 'ArrayRef'
+ => via { HTTP::Headers->new( @{ $_ } ) }
+ => from 'HashRef'
+ => via { HTTP::Headers->new( %{ $_ } ) };
+
+We first tell it that we are attaching the coercion to the 'Header'
+subtype. We then give is a set of C<from> clauses which map other
+subtypes to coercion routines (through the C<via> keyword). Fairly
+simple really, however, this alone does nothing. We have to tell
+our attribute declaration to actually use the coercion, like so:
+
+ has 'headers' => (
+ is => 'rw',
+ isa => 'Header',
+ coerce => 1,
+ default => sub { HTTP::Headers->new }
+ );
+
+This will coerce any B<ArrayRef> or B<HashRef> which is passed into
+the C<headers> accessor into an instance of B<HTTP::Headers>. So that
+the following lines of code are all equivalent:
+
+ $foo->headers(HTTP::Headers->new(bar => 1, baz => 2));
+ $foo->headers([ 'bar', 1, 'baz', 2 ]);
+ $foo->headers({ bar => 1, baz => 2 });
+
+As you can see, careful use of coercions can produce an very open
+interface for your class, while still retaining the "safety" of
+your type constraint checks.
+
+Our next coercion takes advantage of the power of CPAN to handle
+the details of our coercion. In this particular case it uses the
+L<Params::Coerce> module, which fits in rather nicely with L<Moose>.
+
+Again, we create a simple subtype to represent instance of the
+B<URI> class.
+
+ subtype 'Uri'
+ => as 'Object'
+ => where { $_->isa('URI') };
+
+Then we add the coercion:
+
+ coerce 'Uri'
+ => from 'Object'
+ => via { $_->isa('URI')
+ ? $_
+ : Params::Coerce::coerce( 'URI', $_ ) }
+ => from 'Str'
+ => via { URI->new( $_, 'http' ) };
+
+The first C<from> clause we introduce is for the 'Object' subtype,
+an 'Object' is simply, anything which is C<bless>ed. This means
+that if the coercion encounters another object, it should use this
+clause. Now we look at the C<via> block so what it does. First
+it checks to see if its a B<URI> instance. Since the coercion
+process happens prior to any type constraint checking, it is entirely
+possible for this to happen. And if it does happen, we simple want
+to pass the instance on through. However, if it is not an instance
+of B<URI>, then we need to coerce it. This is where L<Params::Coerce>
+can do it's magic, and we can just use it's return value. Simple
+really, and much less work since we use a module from CPAN :)
+
+The second C<from> clause is attached to the 'Str' subtype, and
+illustrates how coercions can also be used to handle certain
+'default' behaviors. In this coercion, we simple take any string
+and pass it into the B<URI> constructor along with the default
+'http' scheme type.
+
+And of course, our coercions do nothing unless they are told to,
+like so:
+
+ has 'base' => (is => 'rw', isa => 'Uri', coerce => 1);
+ has 'uri' => (is => 'rw', isa => 'Uri', coerce => 1);
+
+As you can see, re-using the coercion allows us to enforce a
+consistent and very flexible API across multiple accessors.
+
+=head1 CONCLUSION
+
+This recipe illustrated the power of coercions to build a more
+flexible and open API for your accessors, while still retaining
+all the safety that comes from using Moose's type constraints.
+Using coercions it becomes simple to manage (from a single
+location) a consisten API not only across multiple accessors,
+but across multiple classes as well.
+
+In the next recipe, we will introduce roles, a concept originally
+borrowed from Smalltalk, which made it's way into Perl 6, and
+now into Moose.
=head1 AUTHOR