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