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