Commit | Line | Data |
aff0421c |
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/], |
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 | |
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 |
433b5928 |
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! |
aff0421c |
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 |
433b5928 |
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. |
aff0421c |
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 |
433b5928 |
110 | a new attribute metaclass. |
aff0421c |
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 | |
433b5928 |
129 | Much like when we define a new attribute metaclass, we can provide a shorthand |
aff0421c |
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 |
433b5928 |
135 | since recipe 21. |
aff0421c |
136 | |
137 | has url => ( |
138 | traits => [qw/Labeled/], |
aff0421c |
139 | is => 'rw', |
433b5928 |
140 | isa => 'Str', |
aff0421c |
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 |
433b5928 |
146 | by the attribute. We provide a label for the attribute in the same way. |
aff0421c |
147 | |
433b5928 |
148 | # print the label if available |
149 | if ($attribute->does('MyApp::Meta::Attribute::Trait::Labeled') |
150 | && $attribute->has_label) { |
151 | print $attribute->label; |
152 | } |
aff0421c |
153 | |
433b5928 |
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 |
aff0421c |
158 | C<< $attribute->has_label >>. |
159 | |
160 | That's all. Everything else is the same! |
161 | |
d9a8643f |
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 | |
aff0421c |
183 | =head1 CONCLUSION |
184 | |
185 | If you're extending your attributes, it's easier and more flexible to provide |
433b5928 |
186 | composable bits of behavior than to subclass L<Moose::Meta::Attribute>. |
aff0421c |
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 | |