1 package Moose::Cookbook::Meta::Recipe3;
3 # ABSTRACT: Labels implemented via attribute traits
12 package MyApp::Meta::Attribute::Trait::Labeled;
18 predicate => 'has_label',
21 package Moose::Meta::Attribute::Custom::Trait::Labeled;
22 sub register_implementation {'MyApp::Meta::Attribute::Trait::Labeled'}
24 package MyApp::Website;
28 traits => [qw/Labeled/],
31 label => "The site's URL",
42 my $meta = $self->meta;
46 for my $attribute ( map { $meta->get_attribute($_) }
47 sort $meta->get_attribute_list ) {
49 if ( $attribute->does('MyApp::Meta::Attribute::Trait::Labeled')
50 && $attribute->has_label ) {
51 $dump .= $attribute->label;
54 $dump .= $attribute->name;
57 my $reader = $attribute->get_read_method;
58 $dump .= ": " . $self->$reader . "\n";
66 my $app = MyApp::Website->new( url => "http://google.com", name => "Google" );
70 This recipe is a variation on
71 L<Moose::Cookbook::Meta::Recipe2>. Please read that recipe first.
75 In L<Moose::Cookbook::Meta::Recipe2>, we created an attribute
76 metaclass which lets you provide a label for attributes.
78 Using a metaclass works fine until you realize you want to add a label
79 I<and> an expiration, or some other combination of new behaviors. You
80 could create yet another metaclass which subclasses those two, but
81 that makes a mess, especially if you want to mix and match behaviors
82 across many attributes.
84 Fortunately, Moose provides a much saner alternative, which is to
85 encapsulate each extension as a role, not a class. We can make a role
86 which adds a label to an attribute, and could make another to
91 Roles that apply to metaclasses have a special name: traits. Don't let
92 the change in nomenclature fool you, B<traits are just roles>.
94 L<Moose/has> allows you to pass a C<traits> parameter for an
95 attribute. This parameter takes a list of trait names which are
96 composed into an anonymous metaclass, and that anonymous metaclass is
97 used for the attribute.
99 Yes, we still have lots of metaclasses in the background, but they're
100 managed by Moose for you.
102 Traits can do anything roles can do. They can add or refine
103 attributes, wrap methods, provide more methods, define an interface,
104 etc. The only difference is that you're now changing the attribute
105 metaclass instead of a user-level class.
109 A side-by-side look of the code examples in this recipe and recipe 2
110 show that defining and using a trait is very similar to a full-blown
113 package MyApp::Meta::Attribute::Trait::Labeled;
119 predicate => 'has_label',
122 Instead of subclassing L<Moose::Meta::Attribute>, we define a role. As
123 with our metaclass in L<recipe 2|Moose::Cookbook::Meta::Recipe2>,
124 registering our role allows us to refer to it by a short name.
126 package Moose::Meta::Attribute::Custom::Trait::Labeled;
127 sub register_implementation { 'MyApp::Meta::Attribute::Trait::Labeled' }
129 Moose looks for the C<register_implementation> method in
130 C<Moose::Meta::Attribute::Custom::Trait::$TRAIT_NAME> to find the full
133 For the rest of the code, we will only cover what is I<different> from
134 L<recipe 2|Moose::Cookbook::Meta::Recipe2>.
137 traits => [qw/Labeled/],
140 label => "The site's URL",
143 Instead of passing a C<metaclass> parameter, this time we pass
144 C<traits>. This contains a list of trait names. Moose will build an
145 anonymous attribute metaclass from these traits and use it for this
146 attribute. Passing a C<label> parameter works just as it did with the
149 if ( $attribute->does('MyApp::Meta::Attribute::Trait::Labeled')
150 && $attribute->has_label ) {
151 $dump .= $attribute->label;
154 In the metaclass example, we used C<< $attribute->isa >>. With a role,
155 we instead ask if the meta-attribute object C<does> the required
156 role. If it does not do this role, the attribute meta object won't
157 have the C<has_label> method.
159 That's all. Everything else is the same!
161 =head1 TURNING A METACLASS INTO A TRAIT
163 "But wait!" you protest. "I've already written all of my extensions as
164 attribute metaclasses. I don't want to break all that code out there."
166 Fortunately, you can easily turn a metaclass into a trait and still
167 provide the original metaclass:
169 package MyApp::Meta::Attribute::Labeled;
171 extends 'Moose::Meta::Attribute';
172 with 'MyApp::Meta::Attribute::Trait::Labeled';
174 package Moose::Meta::Attribute::Custom::Labeled;
175 sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
177 Unfortunately, going the other way (providing a trait created from a
178 metaclass) is more tricky.
182 If you're extending your attributes, it's easier and more flexible to
183 provide composable bits of behavior than to subclass
184 L<Moose::Meta::Attribute>. Using traits lets you cooperate with other
185 extensions, either from CPAN or that you might write in the
186 future. Moose makes it easy to create attribute metaclasses on the fly
187 by providing a list of trait names to L<Moose/has>.
192 = MyApp::Website->new( url => "http://google.com", name => "Google" );
194 $app2->dump, q{name: Google
195 The site's URL: http://google.com
196 }, '... got the expected dump value'