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