6 Moose::Cookbook::Meta::Recipe3 - Labels implemented via attribute traits
10 package MyApp::Meta::Attribute::Trait::Labeled;
16 predicate => 'has_label',
19 package Moose::Meta::Attribute::Custom::Trait::Labeled;
20 sub register_implementation {'MyApp::Meta::Attribute::Trait::Labeled'}
22 package MyApp::Website;
26 traits => [qw/Labeled/],
29 label => "The site's URL",
42 my %attributes = %{ $self->meta->get_attribute_map };
43 for my $name ( sort keys %attributes ) {
44 my $attribute = $attributes{$name};
46 if ( $attribute->does('MyApp::Meta::Attribute::Trait::Labeled')
47 && $attribute->has_label ) {
48 $dump .= $attribute->label;
54 my $reader = $attribute->get_read_method;
55 $dump .= ": " . $self->$reader . "\n";
63 my $app = MyApp::Website->new( url => "http://google.com", name => "Google" );
67 This recipe is a variation on
68 L<Moose::Cookbook::Meta::Recipe2>. Please read that recipe first.
72 In L<Moose::Cookbook::Meta::Recipe2>, we created an attribute
73 metaclass which lets you provide a label for attributes.
75 Using a metaclass works fine until you realize you want to add a label
76 I<and> an expiration, or some other combination of new behaviors. You
77 could create yet another metaclass which subclasses those two, but
78 that makes a mess, especially if you want to mix and match behaviors
79 across many attributes.
81 Fortunately, Moose provides a much saner alternative, which is to
82 encapsulate each extension as a role, not a class. We can make a role
83 which adds a label to an attribute, and could make another to
88 Roles that apply to metaclasses have a special name: traits. Don't let
89 the change in nomenclature fool you, B<traits are just roles>.
91 L<Moose/has> allows you to pass a C<traits> parameter for an
92 attribute. This parameter takes a list of trait names which are
93 composed into an anonymous metaclass, and that anonymous metaclass is
94 used for the attribute.
96 Yes, we still have lots of metaclasses in the background, but they're
97 managed by Moose for you.
99 Traits can do anything roles can do. They can add or refine
100 attributes, wrap methods, provide more methods, define an interface,
101 etc. The only difference is that you're now changing the attribute
102 metaclass instead of a user-level class.
106 A side-by-side look of the code examples in this recipe and recipe 2
107 show that defining and using a trait is very similar to a full-blown
110 package MyApp::Meta::Attribute::Trait::Labeled;
116 predicate => 'has_label',
119 Instead of subclassing L<Moose::Meta::Attribute>, we define a role. As
120 with our metaclass in L<recipe 2|Moose::Cookbook::Meta::Recipe2>,
121 registering our role allows us to refer to it by a short name.
123 package Moose::Meta::Attribute::Custom::Trait::Labeled;
124 sub register_implementation { 'MyApp::Meta::Attribute::Trait::Labeled' }
126 Moose looks for the C<register_implementation> method in
127 C<Moose::Meta::Attribute::Custom::Trait::$TRAIT_NAME> to find the full
130 For the rest of the code, we will only cover what is I<different> from
131 L<recipe 2|Moose::Cookbook::Meta::Recipe2>.
134 traits => [qw/Labeled/],
137 label => "The site's URL",
140 Instead of passing a C<metaclass> parameter, this time we pass
141 C<traits>. This contains a list of trait names. Moose will build an
142 anonymous attribute metaclass from these traits and use it for this
143 attribute. Passing a C<label> parameter works just as it did with the
146 if ( $attribute->does('MyApp::Meta::Attribute::Trait::Labeled')
147 && $attribute->has_label ) {
148 $dump .= $attribute->label;
151 In the metaclass example, we used C<< $attribute->isa >>. With a role,
152 we instead ask if the meta-attribute object C<does> the required
153 role. If it does not do this role, the attribute meta object won't
154 have the C<has_label> method.
156 That's all. Everything else is the same!
158 =head1 TURNING A METACLASS INTO A TRAIT
160 "But wait!" you protest. "I've already written all of my extensions as
161 attribute metaclasses. I don't want to break all that code out there."
163 Fortunately, you can easily turn a metaclass into a trait and still
164 provide the original metaclass:
166 package MyApp::Meta::Attribute::Labeled;
168 extends 'Moose::Meta::Attribute';
169 with 'MyApp::Meta::Attribute::Trait::Labeled';
171 package Moose::Meta::Attribute::Custom::Labeled;
172 sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
174 Unfortunately, going the other way (providing a trait created from a
175 metaclass) is more tricky.
179 If you're extending your attributes, it's easier and more flexible to
180 provide composable bits of behavior than to subclass
181 L<Moose::Meta::Attribute>. Using traits lets you cooperate with other
182 extensions, either from CPAN or that you might write in the
183 future. Moose makes it easy to create attribute metaclasses on the fly
184 by providing a list of trait names to L<Moose/has>.
188 Shawn M Moore E<lt>sartak@gmail.comE<gt>
190 Dave Rolsky E<lt>autarch@urth.org<gt>
192 =head1 COPYRIGHT AND LICENSE
194 Copyright 2006-2009 by Infinity Interactive, Inc.
196 L<http://www.iinteractive.com>
198 This library is free software; you can redistribute it and/or modify
199 it under the same terms as Perl itself.
204 = MyApp::Website->new( url => "http://google.com", name => "Google" );
206 $app2->dump, q{name: Google
207 The site's URL: http://google.com
208 }, 'got the expected dump value'