=pod =head1 NAME Moose::Cookbook::Meta::Recipe3 - Labels implemented via attribute traits =head1 SYNOPSIS package MyApp::Meta::Attribute::Trait::Labeled; use Moose::Role; has label => ( is => 'rw', isa => 'Str', predicate => 'has_label', ); package Moose::Meta::Attribute::Custom::Trait::Labeled; sub register_implementation {'MyApp::Meta::Attribute::Trait::Labeled'} package MyApp::Website; use Moose; use MyApp::Meta::Attribute::Trait::Labeled; has url => ( traits => [qw/Labeled/], is => 'rw', isa => 'Str', label => "The site's URL", ); has name => ( is => 'rw', isa => 'Str', ); sub dump { my $self = shift; # iterate over all the attributes in $self my %attributes = %{ $self->meta->get_attribute_map }; while ( my ( $name, $attribute ) = each %attributes ) { # print the label if available if ( $attribute->does('MyApp::Meta::Attribute::Trait::Labeled') && $attribute->has_label ) { print $attribute->label; } # otherwise print the name else { print $name; } # print the attribute's value my $reader = $attribute->get_read_method; print ": " . $self->$reader . "\n"; } } package main; my $app = MyApp::Website->new( url => "http://google.com", name => "Google" ); $app->dump; =head1 BUT FIRST This recipe is a continuation of L. Please read that recipe first. =head1 MOTIVATION In L, we created an attribute metaclass that gives attributes a "label" that can be set in L. 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, 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 a list of roles to apply to the attribute metaclass... =head1 TRAITS Roles that apply to metaclasses have a special name: traits. Don't let the change in nomenclature fool you, B. L provides a C 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. 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. =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. package MyApp::Meta::Attribute::Trait::Labeled; use Moose::Role; has label => ( is => 'rw', isa => 'Str', predicate => 'has_label', ); Instead of subclassing L, 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. 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 method in C 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. has url => ( traits => [qw/Labeled/], is => 'rw', isa => 'Str', label => "The site's URL", ); L provides a C 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. # print the label if available if ( $attribute->does('MyApp::Meta::Attribute::Trait::Labeled') && $attribute->has_label ) { 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 role?" If not, the attribute metaclass won't have the C method, and so it would be an error to blindly call C<< $attribute->has_label >>. That's all. Everything else is the same! =head1 METACLASS + 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. package MyApp::Meta::Attribute::Labeled; use Moose; extends 'Moose::Meta::Attribute'; with 'MyApp::Meta::Attribute::Trait::Labeled'; 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. =head1 CONCLUSION If you're extending your attributes, it's easier and more flexible to provide composable bits of behavior than to subclass L. 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. =head1 AUTHOR Shawn M Moore Esartak@gmail.comE =head1 COPYRIGHT AND LICENSE Copyright 2006-2009 by Infinity Interactive, Inc. L This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut