Commit | Line | Data |
aff0421c |
1 | |
2 | =pod |
3 | |
4 | =head1 NAME |
5 | |
43aa5bf9 |
6 | Moose::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 |
67 | This recipe is a continuation of |
68 | L<Moose::Cookbook::Meta::Recipe2>. Please read that recipe first. |
aff0421c |
69 | |
70 | =head1 MOTIVATION |
71 | |
09412052 |
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! |
aff0421c |
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 |
433b5928 |
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. |
aff0421c |
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 | |
09412052 |
109 | A side-by-side look of the code examples in this recipe and recipe 2 should |
aff0421c |
110 | indicate that defining and using a trait is very similar to defining and using |
433b5928 |
111 | a 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 | |
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 | |
433b5928 |
130 | Much like when we define a new attribute metaclass, we can provide a shorthand |
aff0421c |
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 |
09412052 |
136 | since 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 | |
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 |
433b5928 |
147 | by 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 |
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 |
aff0421c |
159 | C<< $attribute->has_label >>. |
160 | |
161 | That'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 |
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 | |
aff0421c |
184 | =head1 CONCLUSION |
185 | |
186 | If you're extending your attributes, it's easier and more flexible to provide |
433b5928 |
187 | composable bits of behavior than to subclass L<Moose::Meta::Attribute>. |
aff0421c |
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 | |