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 | |
6a7e3999 |
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; |
aff0421c |
65 | |
66 | =head1 BUT FIRST |
67 | |
09412052 |
68 | This recipe is a continuation of |
69 | L<Moose::Cookbook::Meta::Recipe2>. Please read that recipe first. |
aff0421c |
70 | |
71 | =head1 MOTIVATION |
72 | |
09412052 |
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! |
aff0421c |
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 |
433b5928 |
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. |
aff0421c |
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 | |
09412052 |
110 | A side-by-side look of the code examples in this recipe and recipe 2 should |
aff0421c |
111 | indicate that defining and using a trait is very similar to defining and using |
433b5928 |
112 | a new attribute metaclass. |
aff0421c |
113 | |
6a7e3999 |
114 | package MyApp::Meta::Attribute::Trait::Labeled; |
115 | use Moose::Role; |
aff0421c |
116 | |
6a7e3999 |
117 | has label => ( |
118 | is => 'rw', |
119 | isa => 'Str', |
120 | predicate => 'has_label', |
121 | ); |
aff0421c |
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 | |
6a7e3999 |
128 | package Moose::Meta::Attribute::Custom::Trait::Labeled; |
129 | sub register_implementation { 'MyApp::Meta::Attribute::Trait::Labeled' } |
aff0421c |
130 | |
433b5928 |
131 | Much like when we define a new attribute metaclass, we can provide a shorthand |
aff0421c |
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 |
09412052 |
137 | since recipe 2. |
aff0421c |
138 | |
6a7e3999 |
139 | has url => ( |
140 | traits => [qw/Labeled/], |
141 | is => 'rw', |
142 | isa => 'Str', |
143 | label => "The site's URL", |
144 | ); |
aff0421c |
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 |
433b5928 |
148 | by the attribute. We provide a label for the attribute in the same way. |
aff0421c |
149 | |
6a7e3999 |
150 | # print the label if available |
151 | if ( $attribute->does('MyApp::Meta::Attribute::Trait::Labeled') |
152 | && $attribute->has_label ) { |
153 | print $attribute->label; |
154 | } |
aff0421c |
155 | |
433b5928 |
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 |
aff0421c |
160 | C<< $attribute->has_label >>. |
161 | |
162 | That's all. Everything else is the same! |
163 | |
d9a8643f |
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 | |
6a7e3999 |
173 | package MyApp::Meta::Attribute::Labeled; |
174 | use Moose; |
175 | extends 'Moose::Meta::Attribute'; |
176 | with 'MyApp::Meta::Attribute::Trait::Labeled'; |
d9a8643f |
177 | |
6a7e3999 |
178 | package Moose::Meta::Attribute::Custom::Labeled; |
179 | sub register_implementation { 'MyApp::Meta::Attribute::Labeled' } |
d9a8643f |
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 | |
aff0421c |
185 | =head1 CONCLUSION |
186 | |
187 | If you're extending your attributes, it's easier and more flexible to provide |
433b5928 |
188 | composable bits of behavior than to subclass L<Moose::Meta::Attribute>. |
aff0421c |
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 | |
2840a3b2 |
200 | Copyright 2006-2009 by Infinity Interactive, Inc. |
aff0421c |
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 | |