tidy code in pod for consistency, also tidy some text
[gitmo/Moose.git] / lib / Moose / Cookbook / Meta / Recipe3.pod
CommitLineData
aff0421c 1
2=pod
3
4=head1 NAME
5
43aa5bf9 6Moose::Cookbook::Meta::Recipe3 - Labels implemented via attribute traits
aff0421c 7
8=head1 SYNOPSIS
9
10 package MyApp::Meta::Attribute::Trait::Labeled;
11 use Moose::Role;
12
13 has label => (
14 is => 'rw',
15 isa => 'Str',
16 predicate => 'has_label',
17 );
18
19 package Moose::Meta::Attribute::Custom::Trait::Labeled;
20 sub register_implementation { 'MyApp::Meta::Attribute::Trait::Labeled' }
21
22 package MyApp::Website;
23 use Moose;
24 use MyApp::Meta::Attribute::Trait::Labeled;
25
26 has url => (
27 traits => [qw/Labeled/],
aff0421c 28 is => 'rw',
433b5928 29 isa => 'Str',
aff0421c 30 label => "The site's URL",
31 );
32
33 has name => (
34 is => 'rw',
35 isa => 'Str',
36 );
37
38 sub dump {
39 my $self = shift;
40
41 # iterate over all the attributes in $self
42 my %attributes = %{ $self->meta->get_attribute_map };
43 while (my ($name, $attribute) = each %attributes) {
44
45 # print the label if available
46 if ($attribute->does('MyApp::Meta::Attribute::Trait::Labeled')
47 && $attribute->has_label) {
48 print $attribute->label;
49 }
50 # otherwise print the name
51 else {
52 print $name;
53 }
54
55 # print the attribute's value
56 my $reader = $attribute->get_read_method;
57 print ": " . $self->$reader . "\n";
58 }
59 }
60
61 package main;
62 my $app = MyApp::Website->new(url => "http://google.com", name => "Google");
63 $app->dump;
64
65=head1 BUT FIRST
66
09412052 67This recipe is a continuation of
68L<Moose::Cookbook::Meta::Recipe2>. Please read that recipe first.
aff0421c 69
70=head1 MOTIVATION
71
09412052 72In L<Moose::Cookbook::Meta::Recipe2>, we created an attribute
73metaclass that gives attributes a "label" that can be set in
74L<Moose/has>. That works well until you want a second meta-attribute,
75or until you want to adjust the behavior of the attribute. You could
76define a specialized attribute metaclass to use in every attribute.
77However, you may want different attributes to have different
78behaviors. You might end up with a unique attribute metaclass for
79B<every single attribute>, with a lot of code copying and pasting!
aff0421c 80
81Or, if you've been drinking deeply of the Moose kool-aid, you'll have a role
82for each of the behaviors. One role would give a label meta-attribute. Another
83role would signify that this attribute is not directly modifiable via the
84REST interface. Another role would write to a logfile when this attribute
85was read.
86
87Unfortunately, you'd still be left with a bunch of attribute metaclasses that
88do nothing but compose a bunch of roles. If only there were some way to specify
89in L<Moose/has> a list of roles to apply to the attribute metaclass...
90
91=head1 TRAITS
92
93Roles that apply to metaclasses have a special name: traits. Don't let the
94change in nomenclature fool you, B<traits are just roles>.
95
96L<Moose/has> provides a C<traits> option. It takes a list of trait names to
433b5928 97compose into an anonymous metaclass. That means you do still have a bunch of
98attribute metaclasses that do nothing but compose a bunch of roles, but they're
99managed automatically by Moose. You don't need to declare them in advance, or
100worry whether changing one will affect some other attribute.
aff0421c 101
102What can traits do? Anything roles can do. They can add or refine attributes,
103wrap methods, provide more methods, define an interface, etc. The only
104difference is that you're now changing the attribute metaclass instead of a
105user-level class.
106
107=head1 DISSECTION
108
09412052 109A side-by-side look of the code examples in this recipe and recipe 2 should
aff0421c 110indicate that defining and using a trait is very similar to defining and using
433b5928 111a new attribute metaclass.
aff0421c 112
113 package MyApp::Meta::Attribute::Trait::Labeled;
114 use Moose::Role;
115
116 has label => (
117 is => 'rw',
118 isa => 'Str',
119 predicate => 'has_label',
120 );
121
122Instead of subclassing L<Moose::Meta::Attribute>, we define a role. Traits
123don't need any special methods or attributes. You just focus on whatever it is
124you actually need to get done. Here we're adding a new meta-attribute for use
125in our application.
126
127 package Moose::Meta::Attribute::Custom::Trait::Labeled;
128 sub register_implementation { 'MyApp::Meta::Attribute::Trait::Labeled' }
129
433b5928 130Much like when we define a new attribute metaclass, we can provide a shorthand
aff0421c 131name for the trait. Moose looks at the C<register_implementation> method in
132C<Moose::Meta::Attribute::Custom::Trait::$TRAIT_NAME> to find the full
133name of the trait.
134
135Now we begin writing our application logic. I'll only cover what has changed
09412052 136since recipe 2.
aff0421c 137
138 has url => (
139 traits => [qw/Labeled/],
aff0421c 140 is => 'rw',
433b5928 141 isa => 'Str',
aff0421c 142 label => "The site's URL",
143 );
144
145L<Moose/has> provides a C<traits> option. Just pass the list of trait names and
146it will compose them together to form the (anonymous) attribute metaclass used
433b5928 147by the attribute. We provide a label for the attribute in the same way.
aff0421c 148
433b5928 149 # print the label if available
150 if ($attribute->does('MyApp::Meta::Attribute::Trait::Labeled')
151 && $attribute->has_label) {
152 print $attribute->label;
153 }
aff0421c 154
433b5928 155Previously, this code asked the question "Does this attribute use our attribute
156metaclass?" Since we're now using a trait, we ask "Does this attribute's
157metaclass do the C<Labeled> role?" If not, the attribute metaclass won't have
158the C<has_label> method, and so it would be an error to blindly call
aff0421c 159C<< $attribute->has_label >>.
160
161That's all. Everything else is the same!
162
d9a8643f 163=head1 METACLASS + TRAIT
164
165"But wait!" you protest. "I've already written all of my extensions as
166attribute metaclasses. I don't want to break all that code out there."
167
168All is not lost. If you rewrite your extension as a trait, then you can
169easily get a regular metaclass extension out of it. You just compose the trait
170in the attribute metaclass, as normal.
171
172 package MyApp::Meta::Attribute::Labeled;
173 use Moose;
174 extends 'Moose::Meta::Attribute';
175 with 'MyApp::Meta::Attribute::Trait::Labeled';
176
177 package Moose::Meta::Attribute::Custom::Labeled;
178 sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
179
180Unfortunately, going the other way (providing a trait created from a metaclass)
181is more tricky. Thus, defining your extensions as traits is just plain better
182than defining them as subclassed metaclasses.
183
aff0421c 184=head1 CONCLUSION
185
186If you're extending your attributes, it's easier and more flexible to provide
433b5928 187composable bits of behavior than to subclass L<Moose::Meta::Attribute>.
aff0421c 188Using traits (which are just roles applied to a metaclass!) let you choose
189exactly which behaviors each attribute will have. Moose makes it easy to create
190attribute metaclasses on the fly by providing a list of trait names to
191L<Moose/has>.
192
193=head1 AUTHOR
194
195Shawn M Moore E<lt>sartak@gmail.comE<gt>
196
197=head1 COPYRIGHT AND LICENSE
198
199Copyright 2006-2008 by Infinity Interactive, Inc.
200
201L<http://www.iinteractive.com>
202
203This library is free software; you can redistribute it and/or modify
204it under the same terms as Perl itself.
205
206=cut
207
208