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/], |
28 | isa => 'Str', |
29 | is => 'rw', |
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 |
75 | could have a specialized attribute metaclass for your application that does it |
76 | all. However, you may want different attributes to have different behaviors. So |
77 | you may end up with a unique attribute metaclass for B<every single attribute>, |
78 | with a lot of code copying and pasting. |
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 |
96 | compose into an anonymous metaclass. So you do still have a bunch of attribute |
97 | metaclasses that do nothing but compose a bunch of roles, but they're managed |
98 | automatically by Moose. You don't need to declare them in advance, or worry |
99 | whether changing one will affect some other attribute. |
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 |
110 | a new attribute metaclass. Only a few lines have changed. |
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 | |
129 | Much like when we define a new attribute metaclass, we can provide a shortcut |
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 |
135 | between recipe 21 and this. |
136 | |
137 | has url => ( |
138 | traits => [qw/Labeled/], |
139 | isa => 'Str', |
140 | is => 'rw', |
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 |
146 | by this attribute. |
147 | |
148 | # print the label if available |
149 | if ($attribute->does('MyApp::Meta::Attribute::Trait::Labeled') |
150 | && $attribute->has_label) { |
151 | print $attribute->label; |
152 | } |
153 | |
154 | Previously, this asked the question "Does this attribute use our attribute |
155 | metaclass?" But since we're now using a trait, we ask "Does this attribute's |
156 | metaclass do the C<Labeled> role?" If not, it won't have the C<has_label> |
157 | method, and so it would be an error to blindly call |
158 | C<< $attribute->has_label >>. |
159 | |
160 | That's all. Everything else is the same! |
161 | |
162 | =head1 CONCLUSION |
163 | |
164 | If you're extending your attributes, it's easier and more flexible to provide |
165 | composable bits of behavior, rather than subclassing L<Moose::Meta::Attribute>. |
166 | Using traits (which are just roles applied to a metaclass!) let you choose |
167 | exactly which behaviors each attribute will have. Moose makes it easy to create |
168 | attribute metaclasses on the fly by providing a list of trait names to |
169 | L<Moose/has>. |
170 | |
171 | =head1 AUTHOR |
172 | |
173 | Shawn M Moore E<lt>sartak@gmail.comE<gt> |
174 | |
175 | =head1 COPYRIGHT AND LICENSE |
176 | |
177 | Copyright 2006-2008 by Infinity Interactive, Inc. |
178 | |
179 | L<http://www.iinteractive.com> |
180 | |
181 | This library is free software; you can redistribute it and/or modify |
182 | it under the same terms as Perl itself. |
183 | |
184 | =cut |
185 | |
186 | |