X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FMoose%2FCookbook%2FRecipe6.pod;h=8e5811e2a2b52abc721e191090072674daf09c1c;hb=8abe9636abcd0945739dfeaf971eddbe1fd0cc9e;hp=0e3b0a329eb49fa2214eaddf5666eb24971175dc;hpb=20cf91c1709e499d00f01d159efc246258ab4199;p=gitmo%2FMoose.git diff --git a/lib/Moose/Cookbook/Recipe6.pod b/lib/Moose/Cookbook/Recipe6.pod index 0e3b0a3..8e5811e 100644 --- a/lib/Moose/Cookbook/Recipe6.pod +++ b/lib/Moose/Cookbook/Recipe6.pod @@ -6,93 +6,195 @@ Moose::Cookbook::Recipe6 - The Moose::Role example =head1 SYNOPSIS - - package Constraint; - use strict; - use warnings; + + package Eq; use Moose::Role; - has 'value' => (isa => 'Int', is => 'ro'); + requires 'equal_to'; - around 'validate' => sub { - my $c = shift; - my ($self, $field) = @_; - return undef if $c->($self, $self->validation_value($field)); - return $self->error_message; - }; - - sub validation_value { - my ($self, $field) = @_; - return $field; + sub not_equal_to { + my ($self, $other) = @_; + not $self->equal_to($other); } - sub error_message { confess "Abstract method!" } - - package Constraint::OnLength; - use strict; - use warnings; + package Comparable; use Moose::Role; - has 'units' => (isa => 'Str', is => 'ro'); + with 'Eq'; - override 'validation_value' => sub { - return length(super()); - }; + requires 'compare'; - override 'error_message' => sub { - my $self = shift; - return super() . ' ' . $self->units; - }; - - package Constraint::AtLeast; - use strict; - use warnings; - use Moose; + sub equal_to { + my ($self, $other) = @_; + $self->compare($other) == 0; + } - with 'Constraint'; + sub greater_than { + my ($self, $other) = @_; + $self->compare($other) == 1; + } - sub validate { - my ($self, $field) = @_; - ($field >= $self->value); + sub less_than { + my ($self, $other) = @_; + $self->compare($other) == -1; } - sub error_message { 'must be at least ' . (shift)->value; } + sub greater_than_or_equal_to { + my ($self, $other) = @_; + $self->greater_than($other) || $self->equal_to($other); + } - package Constraint::NoMoreThan; - use strict; - use warnings; + sub less_than_or_equal_to { + my ($self, $other) = @_; + $self->less_than($other) || $self->equal_to($other); + } + + package Printable; + use Moose::Role; + + requires 'to_string'; + + package US::Currency; use Moose; - with 'Constraint'; + with 'Comparable', 'Printable'; + + has 'amount' => (is => 'rw', isa => 'Num', default => 0); - sub validate { - my ($self, $field) = @_; - ($field <= $self->value); + sub compare { + my ($self, $other) = @_; + $self->amount <=> $other->amount; } - sub error_message { 'must be no more than ' . (shift)->value; } + sub to_string { + my $self = shift; + sprintf '$%0.2f USD' => $self->amount + } + +=head1 DESCRIPTION + +In this recipe we examine the role support provided in Moose. "Roles" may be +described in many ways, but there are two main ways in which they are used: as +interfaces, and as a means of code reuse. This recipe demonstrates the +construction and incorporation of roles that define comparison and display of +objects. + +Let's start by examining B. You'll notice that instead of the familiar C you might be expecting, here we use C to make it clear that +this is a role. We encounter a new keyword, C: + + requires 'equal_to'; + +What this does is to indicate that any class which "consumes" (that is to say, +"includes using C", as we'll see a little later) the B role I +include an C method, whether this is provided by the class itself, one +of its superclasses, or another role consumed by the class (1). + +In addition to requiring an C method, B defines a C +method, which simply inverts the result of C. Defining additional +methods in this way, by using only a few base methods that target classes must +define, is a useful pattern to provide maximum functionality with minimum +effort. + +After the minimal B, we next move on to B. The first thing you +will notice is another new keyword, C: + + with 'Eq'; + +C is used to provide a list of roles which this class (or role) consumes. +Here, B only consumes one role (B). In effect, it is as if we +defined a C method within Comparable, and also promised to fulfill +the requirement of an C method. + +B itself states that it requires C. Again, it means that +any classes consuming this role must implement a C method. + + requires 'compare'; + +B defines an C method which satisfies the B role's +requirements. This, along with a number of other methods (C, +C, C, and C) is +simply defined in terms of C, once again demonstrating the pattern of +defining a number of utility methods in terms of only a single method that the +target class need implement. + + sub equal_to { + my ($self, $other) = @_; + $self->compare($other) == 0; + } - package Constraint::LengthNoMoreThan; - use strict; - use warnings; - use Moose; + sub greater_than { + my ($self, $other) = @_; + $self->compare($other) == 1; + } - extends 'Constraint::NoMoreThan'; - with 'Constraint::OnLength'; - - package Constraint::LengthAtLeast; - use strict; - use warnings; - use Moose; + sub less_than { + my ($self, $other) = @_; + $self->compare($other) == -1; + } - extends 'Constraint::AtLeast'; - with 'Constraint::OnLength'; + sub greater_than_or_equal_to { + my ($self, $other) = @_; + $self->greater_than($other) || $self->equal_to($other); + } -=head1 DESCRIPTION + sub less_than_or_equal_to { + my ($self, $other) = @_; + $self->less_than($other) || $self->equal_to($other); + } + +Next up is B. This is a very simple role, akin to B. It merely +requires a C method. + +Finally, we come to B, a class that allows us to reap the benefits +of our hard work. This is a regular Moose class, so we include the normal C. It consumes both B and B, as the following line +shows: + + with 'Comparable', 'Printable'; + +It also defines a regular Moose attribute, C, with a type constraint of +C and a default of C<0>: + + has 'amount' => (is => 'rw', isa => 'Num', default => 0); + +Now we come to the core of the class. First up, we define a C method: + + sub compare { + my ($self, $other) = @_; + $self->amount <=> $other->amount; + } + +As you can see, it simply compares the C attribute of this object with +the C attribute of the other object passed to it. With the single +definition of this method, we gain the following methods for free: C, +C, C, C and +C. + +We end the class with a definition of the C method, which formats the +C attribute for display: + + sub to_string { + my $self = shift; + sprintf '$%0.2f USD' => $self->amount + } + +=head1 CONCLUSION + +This recipe has shown that roles can be very powerful and immensely useful, and +save a great deal of repetition. + +=head1 FOOTNOTES + +=over 4 + +=item (1) -Coming Soon. +At present, method requirements from roles cannot be satisfied by attribute +accessors. This is a limitation of Moose, and will most likely be rectified in a +future release. -(the other 4 recipes kinda burned me out a bit) +=back =head1 AUTHOR @@ -100,7 +202,7 @@ Stevan Little Estevan@iinteractive.comE =head1 COPYRIGHT AND LICENSE -Copyright 2006 by Infinity Interactive, Inc. +Copyright 2006-2008 by Infinity Interactive, Inc. L