fixed the test for method modifiers
[gitmo/Moose.git] / lib / Moose / Cookbook / Recipe11.pod
index b2d1e4c..0db6c62 100644 (file)
 
 =head1 NAME
 
-Moose::Cookbook::Recipe11 - The meta-attribute example
+Moose::Cookbook::Recipe11 - Advanced Role Composition - method exclusion and aliasing
 
 =head1 SYNOPSIS
 
-    package MyApp::Meta::Attribute::Labeled;
-    use Moose;
-    extends 'Moose::Meta::Attribute';
-
-    has label => (
-        is  => 'rw',
-        isa => 'Str',
-        predicate => 'has_label',
-    );
+  package Restartable;
+  use Moose::Role;
 
-    package Moose::Meta::Attribute::Custom::Labeled;
-    sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
+  has 'is_paused' => (
+      is      => 'rw',
+      isa     => 'Boo',
+      default => 0,
+  );
 
-    package MyApp::Website;
-    use Moose;
-    use MyApp::Meta::Attribute::Labeled;
+  requires 'save_state', 'load_state';
 
-    has url => (
-        metaclass => 'Labeled',
-        isa => 'Str',
-        is => 'rw',
-        label => "The site's URL",
-    );
+  sub stop { ... }
 
-    has name => (
-        is => 'rw',
-        isa => 'Str',
-    );
+  sub start { ... }
 
-    sub dump {
-        my $self = shift;
+  package Restartable::ButUnreliable;
+  use Moose::Role;
 
-        # iterate over all the attributes in $self
-        my %attributes = %{ $self->meta->get_attribute_map };
-        while (my ($name, $attribute) = each %attributes) {
+  with 'Restartable' => { alias  => { stop  => '_stop',
+                                      start => '_start' } };
 
-            # print the label if available
-            if ($attribute->isa('MyApp::Meta::Attribute::Labeled')
-                && $attribute->has_label) {
-                    print $attribute->label;
-            }
-            # otherwise print the name
-            else {
-                print $name;
-            }
+  sub stop {
+      my $self = shift;
 
-            # print the attribute's value
-            my $reader = $attribute->get_read_method;
-            print ": " . $self->$reader . "\n";
-        }
-    }
+      $self->explode() if rand(1) > .5;
 
-    package main;
-    my $app = MyApp::Website->new(url => "http://google.com", name => "Google");
-    $app->dump;
+      $self->_stop();
+  }
 
-=head1 SUMMARY
+  sub start {
+      my $self = shift;
 
-In this recipe, we begin to really delve into the wonder of meta-programming.
-Some readers may scoff and claim that this is the arena only of the most
-twisted Moose developers. Absolutely not! Any sufficiently twisted developer
-can benefit greatly from going more meta.
+      $self->explode() if rand(1) > .5;
 
-The high-level goal of this recipe's code is to allow each attribute to have a
-human-readable "label" attached to it. Such labels would be used when showing
-data to an end user. In this recipe we label the "url" attribute with "The
-site's URL" and create a simple method to demonstrate how to use that label.
+      $self->_start();
+  }
 
-=head1 REAL ATTRIBUTES 101
+  package Restartable::ButBroken;
+  use Moose::Role;
 
-All the attributes of a Moose-based object are actually objects themselves.
-These objects have methods and (surprisingly) attributes. Let's look at a
-concrete example.
+  with 'Restartable' => { excludes => [ 'stop', 'start' ] };
 
-    has 'x' => (isa => 'Int', is => 'ro');
-    has 'y' => (isa => 'Int', is => 'rw');
+  sub stop {
+      my $self = shift;
 
-Ahh, the veritable x and y of the Point example. Internally, every Point has an
-x object and a y object. They have methods (such as "get_value") and attributes
-(such as "is_lazy"). What class are they instances of?
-L<Moose::Meta::Attribute>.  You don't normally see the objects lurking behind
-the scenes, because you usually just use C<< $point->x >> and C<< $point->y >>
-and forget that there's a lot of machinery lying in such methods.
+      $self->explode();
+  }
 
-So you have a C<$point> object, which has C<x> and C<y> methods. How can you
-actually access the objects behind these attributes? Here's one way:
+  sub start {
+      my $self = shift;
 
-    $point->meta->get_attribute_map()
+      $self->explode();
+  }
 
-C<get_attribute_map> returns a hash reference that maps attribute names to
-their objects. In our case, C<get_attribute_map> might return something that
-looks like the following:
+=head1 DESCRIPTION
 
-    {
-        x => Moose::Meta::Attribute=HASH(0x196c23c),
-        y => Moose::Meta::Attribute=HASH(0x18d1690),
-    }
+Sometimes when you include a role in a class, you may want to leave
+out some of its methods. In this example, we have a role C<Restartable>
+which provides an C<is_paused> attribute, and two methods, C<stop> and
+C<start>. The implementation of those two methods is irrelevant.
 
-Another way to get a handle on an attribute's object is
-C<< $self->meta->get_attribute('name') >>. Here's one thing you can do now that
-you can interact with the attribute's object directly:
-
-    print $point->meta->get_attribute('x')->type_constraint;
-       => Int
-
-(As an aside, it's not called C<< ->isa >> because C<< $obj->isa >> is already
-taken)
+Then we have two more roles which also implement the same interface,
+each putting their own spin on the C<stop> and C<start> method.
 
-So to actually beef up attributes, what we need to do is:
+In the C<Restartable::ButUnreliable> role, we want to provide a new
+implementation of C<stop> and C<start>, but still have access to the
+original implementation. To do this, we alias the methods from
+C<Restartable> to private methods, and provide wrappers around the
+originals (1).
 
-=over 4
-
-=item Create a new attribute metaclass
-
-=item Create attributes using that new metaclass
-
-=back
-
-Moose makes both of these easy!
-
-Let's start dissecting the recipe's code.
-
-=head1 DISSECTION
-
-We get the ball rolling by creating a new attribute metaclass. It starts off
-somewhat ungloriously.
-
-    package MyApp::Meta::Attribute::Labeled;
-    use Moose;
-    extends 'Moose::Meta::Attribute';
-
-You subclass metaclasses the same way you subclass regular classes. (Extra
-credit: how in the actual hell can you use the MOP to extend itself?)
-
-    has label => (
-        is        => 'rw',
-        isa       => 'Str',
-        predicate => 'has_label',
-    );
-
-Hey, this looks pretty reasonable! This is plain jane Moose code. Recipe 1
-fare. This is merely making a new attribute. An attribute that attributes have.
-A meta-attribute. It may sound scary, but it really isn't! Reread
-L<REAL ATTRIBUTES 101> if this really is terrifying.
-
-The name is "label", it will have a regular accessor, and is a string.
-C<predicate> is a standard part of C<has>. It just creates a method that asks
-the question "Does this attribute have a value?"
-
-    package Moose::Meta::Attribute::Custom::Labeled;
-    sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
-
-This lets Moose discover our new metaclass. That way attributes can actually
-use it. More on what this is doing in a moment.
+In the C<Restartable::ButBroken> role, we want to provide an entirely
+new behavior for C<stop> and C<start>, so we exclude them when
+composing the C<Restartable> role into C<Restartable::ButBroken>.
 
-Note that we're done defining the new metaclass! Only nine lines of code, and
-not particularly difficult lines, either. Now to start using the metaclass.
+It's worth noting that the C<excludes> parameter also accepts a single
+string as an argument if you just want to exclude one method.
 
-    package MyApp::Website;
-    use Moose;
-    use MyApp::Meta::Attribute::Labeled;
-
-Nothing new here. We do have to actually load our metaclass to be able to use
-it.
-
-    has url => (
-        metaclass => 'Labeled',
-        isa => 'Str',
-        is => 'rw',
-        label => "The site's URL",
-    );
-
-Ah ha! Now we're using the metaclass. We're adding a new attribute, C<url>, to
-C<MyApp::Website>. C<has> lets you set the metaclass of the attribute.
-Ordinarily (as we've seen), the metaclass is C<Moose::Meta::Attribute>.
-
-When C<has> sees that you're using a new metaclass, it will take the
-metaclass's name, prepend C<Moose::Meta::Attribute::Custom::>, and call the
-C<register_implementation> function in that package. So here Moose calls
-C<Moose::Meta::Attribute::Custom::Labeled::register_implementation>. We defined
-that function in the beginning -- it just returns our "real" metaclass'
-package, C<MyApp::Meta::Attribute::Labeled>. So Moose uses that metaclass for
-the attribute. It may seem a bit convoluted, but the alternative would be to
-use C<< metaclass => 'MyApp::Meta::Attribute::Labeled' >> on every attribute.
-As usual, Moose optimizes in favor of the end user, not the metaprogrammer. :)
-We also could have just defined the metaclass in
-C<Moose::Meta::Attribute::Custom::Labeled>, but it's probably better to keep to
-your own namespaces.
-
-Finally, we see that C<has> is setting our new meta-attribute, C<label>, to
-C<"The site's URL">. We can access this meta-attribute with:
-
-    $website->meta->get_attribute('url')->label()
-
-Well, back to the code.
-
-    has name => (
-        is => 'rw',
-        isa => 'Str',
-    );
-
-Of course, you don't have to use the new metaclass for B<all> new attributes.
+=head1 CONCLUSION
 
-Now we begin defining a method that will dump the C<MyApp::Website> instance
-for human readers.
+Method exclusion and renaming can come in handy, especially when
+building roles out of other roles. In this example, all of our roles
+implement the C<Restartable> role. Each role provides same API, but
+each has a different implementation under the hood.
 
-    sub dump {
-        my $self = shift;
+You can also use the method aliasing and excluding features when
+composing a role into a class.
 
-        # iterate over all the attributes in $self
-        my %attributes = %{ $self->meta->get_attribute_map };
-        while (my ($name, $attribute) = each %attributes) {
+=head1 FOOTNOTES
 
-Recall that C<get_attribute_map> returns a hashref of attribute names and their
-associated objects.
+=over 4
 
-            # print the label if available
-            if ($attribute->isa('MyApp::Meta::Attribute::Labeled')
-                && $attribute->has_label) {
-                    print $attribute->label;
-            }
+=item (1)
 
-We have two checks here. The first is "is this attribute an instance of
-C<MyApp::Meta::Attribute::Labeled>?". It's good to code defensively. Even if
-all of your attributes have this metaclass, you never know when someone is
-going to subclass your work of art. Poorly. In other words, it's likely that
-there will still be (many) attributes that are instances of the default
-C<Moose::Meta::Attribute>.
-
-The second check is "does this attribute have a label?". This method was
-defined in the new metaclass as the "predicate". If we pass both checks, we
-print the attribute's label.
-
-            # otherwise print the name
-            else {
-                print $name;
-            }
-
-Another good, defensive coding practice: Provide reasonable defaults.
-
-            # print the attribute's value
-            my $reader = $attribute->get_read_method;
-            print ": " . $self->$reader . "\n";
-        }
-    }
-
-Here's another example of using the attribute metaclass.
-C<< $attribute->get_read_method >> returns the name of the method that can
-be invoked on the original object to read the attribute's value.
-C<< $self->$reader >> is an example of "reflection" -- instead of using the
-name of the method, we're using a variable with the name of the method in it.
-Perl doesn't mind. Another way to write this would be
-C<< $self->can($reader)->($self) >>. Yuck. :)
-
-    package main;
-    my $app = MyApp::Website->new(url => "http://google.com", name => "Google");
-    $app->dump;
-
-And we wrap up the example with a script to show off our newfound magic.
+The mention of wrapper should tell you that we could do the same thing
+using method modifiers, but for the sake of this example, we don't.
 
-=head1 CONCLUSION
-
-Why oh why would you want to go through all of these contortions when you can
-just print "The site's URL" directly in the C<dump> method? For one, the DRY
-(Don't Repeat Yourself) principle. If you have it in the C<dump> method, you'll
-probably also have it in the C<as_form> method, and C<to_file>, and so on. So
-why not have a method that maps attribute names to labels? That could work, but
-why not include the label where it belongs, in the attribute's definition?
-That way you're also less likely to forget to add the label.
-
-More importantly, this was a very simple example. Your metaclasses aren't
-limited to just adding new meta-attributes. For example, you could implement
-a metaclass that expires attributes after a certain amount of time. You
-might use it as such:
-
-    has site_cache => (
-        metaclass     => 'TimedExpiry',
-        expires_after => { hours => 1 },
-        refresh_with  => sub { get($_->url) },
-        isa           => 'Str',
-        is            => 'ro',
-    );
-
-The sky's the limit!
+=back
 
 =head1 AUTHOR
 
-Shawn M Moore E<lt>sartak@gmail.comE<gt>
+Dave Rolsky E<lt>autarch@urth.orgE<gt>
 
 =head1 COPYRIGHT AND LICENSE
 
@@ -306,4 +119,3 @@ This library is free software; you can redistribute it and/or modify
 it under the same terms as Perl itself.
 
 =cut
-