Revised recipe 3
Dave Rolsky [Thu, 12 Feb 2009 15:58:13 +0000 (15:58 +0000)]
lib/Moose/Cookbook/Meta/Recipe3.pod

index 9f2645b..bf3bc74 100644 (file)
@@ -65,51 +65,48 @@ Moose::Cookbook::Meta::Recipe3 - Labels implemented via attribute traits
 
 =head1 BUT FIRST
 
-This recipe is a continuation of
+This recipe is a variation on
 L<Moose::Cookbook::Meta::Recipe2>. Please read that recipe first.
 
 =head1 MOTIVATION
 
 In L<Moose::Cookbook::Meta::Recipe2>, we created an attribute
-metaclass that gives attributes a "label" that can be set in
-L<Moose/has>. That works well until you want a second meta-attribute,
-or until you want to adjust the behavior of the attribute. You could
-define a specialized attribute metaclass to use in every attribute.
-However, you may want different attributes to have different
-behaviors. You might end up with a unique attribute metaclass for
-B<every single attribute>, with a lot of code copying and pasting!
-
-Or, if you've been drinking deeply of the Moose kool-aid, you'll have a role
-for each of the behaviors. One role would give a label meta-attribute. Another
-role would signify that this attribute is not directly modifiable via the
-REST interface. Another role would write to a logfile when this attribute
-was read.
-
-Unfortunately, you'd still be left with a bunch of attribute metaclasses that
-do nothing but compose a bunch of roles. If only there were some way to specify
-in L<Moose/has> a list of roles to apply to the attribute metaclass...
+metaclass which lets you provide a label for attributes.
+
+Using a metaclss works fine until you realize you want to add a label
+I<and> an expiration, or some other combination of new behaviors. You
+could create yet another metaclass which subclasses those two, but
+that makes a mess, especially if you want to mix and match behaviors
+across many attributes.
+
+Fortunately, Moose provides a much saner alternative, which is to
+encapsulate each extension as a role, not a class. We can make a role
+which adds a label to an attribute, and could make another to
+implement expiration.
 
 =head1 TRAITS
 
-Roles that apply to metaclasses have a special name: traits. Don't let the
-change in nomenclature fool you, B<traits are just roles>.
+Roles that apply to metaclasses have a special name: traits. Don't let
+the change in nomenclature fool you, B<traits are just roles>.
 
-L<Moose/has> provides a C<traits> option. It takes a list of trait names to
-compose into an anonymous metaclass. That means you do still have a bunch of
-attribute metaclasses that do nothing but compose a bunch of roles, but they're
-managed automatically by Moose. You don't need to declare them in advance, or
-worry whether changing one will affect some other attribute.
+L<Moose/has> allows you to pass a C<traits> parameter for an
+attribute. This parameter takes a list of trait names which are
+composed into an anonymous metaclass, and that anonymous metaclass is
+used for the attribute.
 
-What can traits do? Anything roles can do. They can add or refine attributes,
-wrap methods, provide more methods, define an interface, etc. The only
-difference is that you're now changing the attribute metaclass instead of a
-user-level class.
+Yes, we still have lots of metaclasses in the background, but they're
+managed by Moose for you.
+
+Traits can do anything roles can do. They can add or refine
+attributes, wrap methods, provide more methods, define an interface,
+etc. The only difference is that you're now changing the attribute
+metaclass instead of a user-level class.
 
 =head1 DISSECTION
 
-A side-by-side look of the code examples in this recipe and recipe 2 should
-indicate that defining and using a trait is very similar to defining and using
-a new attribute metaclass.
+A side-by-side look of the code examples in this recipe and recipe 2
+show that defining and using a trait is very similar to a full-blown
+metaclass.
 
   package MyApp::Meta::Attribute::Trait::Labeled;
   use Moose::Role;
@@ -120,21 +117,19 @@ a new attribute metaclass.
       predicate => 'has_label',
   );
 
-Instead of subclassing L<Moose::Meta::Attribute>, we define a role. Traits
-don't need any special methods or attributes. You just focus on whatever it is
-you actually need to get done. Here we're adding a new meta-attribute for use
-in our application.
+Instead of subclassing L<Moose::Meta::Attribute>, we define a role. As
+with our metaclass in L<recipe 2|Moose::Cookbook::Meta::Recipe2>,
+registering our role allows us to refer to it by a short name.
 
   package Moose::Meta::Attribute::Custom::Trait::Labeled;
   sub register_implementation { 'MyApp::Meta::Attribute::Trait::Labeled' }
 
-Much like when we define a new attribute metaclass, we can provide a shorthand
-name for the trait. Moose looks at the C<register_implementation> method in
+Moose looks for the C<register_implementation> method in
 C<Moose::Meta::Attribute::Custom::Trait::$TRAIT_NAME> to find the full
 name of the trait.
 
-Now we begin writing our application logic. I'll only cover what has changed
-since recipe 2.
+For the rest of the code, we will only cover what is I<different> from
+L<recipe 2|Moose::Cookbook::Meta::Recipe2>.
 
   has url => (
       traits => [qw/Labeled/],
@@ -143,9 +138,11 @@ since recipe 2.
       label  => "The site's URL",
   );
 
-L<Moose/has> provides a C<traits> option. Just pass the list of trait names and
-it will compose them together to form the (anonymous) attribute metaclass used
-by the attribute. We provide a label for the attribute in the same way.
+Instead of passing a C<metaclass> parameter, this time we pass
+C<traits>. This contains a list of trait names. Moose will build an
+anonymous attribute metaclass from these traits and use it for this
+attribute. Passing a C<label> parameter works just as it did with the
+metaclass example.
 
   # print the label if available
   if (   $attribute->does('MyApp::Meta::Attribute::Trait::Labeled')
@@ -153,22 +150,20 @@ by the attribute. We provide a label for the attribute in the same way.
       print $attribute->label;
   }
 
-Previously, this code asked the question "Does this attribute use our attribute
-metaclass?" Since we're now using a trait, we ask "Does this attribute's
-metaclass do the C<Labeled> role?" If not, the attribute metaclass won't have
-the C<has_label> method, and so it would be an error to blindly call
-C<< $attribute->has_label >>.
+In the metaclass example, we used C<< $attribute->isa >>. With a role,
+we instead ask if the meta-attribute object C<does> the required
+role. If it does not do this role, the attribute meta object won't
+have the C<has_label> method.
 
 That's all. Everything else is the same!
 
-=head1 METACLASS + TRAIT
+=head1 TURNING A METACLASS INTO A TRAIT
 
 "But wait!" you protest. "I've already written all of my extensions as
 attribute metaclasses. I don't want to break all that code out there."
 
-All is not lost. If you rewrite your extension as a trait, then you can
-easily get a regular metaclass extension out of it. You just compose the trait
-in the attribute metaclass, as normal.
+Fortunately, you can easily turn a metaclass into a trait and still
+provide the original metaclass:
 
   package MyApp::Meta::Attribute::Labeled;
   use Moose;
@@ -178,23 +173,24 @@ in the attribute metaclass, as normal.
   package Moose::Meta::Attribute::Custom::Labeled;
   sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
 
-Unfortunately, going the other way (providing a trait created from a metaclass)
-is more tricky. Thus, defining your extensions as traits is just plain better
-than defining them as subclassed metaclasses.
+Unfortunately, going the other way (providing a trait created from a
+metaclass) is more tricky.
 
 =head1 CONCLUSION
 
-If you're extending your attributes, it's easier and more flexible to provide
-composable bits of behavior than to subclass L<Moose::Meta::Attribute>.
-Using traits (which are just roles applied to a metaclass!) let you choose
-exactly which behaviors each attribute will have. Moose makes it easy to create
-attribute metaclasses on the fly by providing a list of trait names to
-L<Moose/has>.
+If you're extending your attributes, it's easier and more flexible to
+provide composable bits of behavior than to subclass
+L<Moose::Meta::Attribute>. Using traits lets you cooperate with other
+extensions, either from CPAN or that you might write in the
+future. Moose makes it easy to create attribute metaclasses on the fly
+by providing a list of trait names to L<Moose/has>.
 
 =head1 AUTHOR
 
 Shawn M Moore E<lt>sartak@gmail.comE<gt>
 
+Dave Rolsky E<lt>autarch@urth.org<gt>
+
 =head1 COPYRIGHT AND LICENSE
 
 Copyright 2006-2009 by Infinity Interactive, Inc.