=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;
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/],
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')
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;
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.