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; |
6a7e3999 |
24 | |
25 | has url => ( |
26 | traits => [qw/Labeled/], |
27 | is => 'rw', |
28 | isa => 'Str', |
29 | label => "The site's URL", |
30 | ); |
31 | |
32 | has name => ( |
33 | is => 'rw', |
34 | isa => 'Str', |
35 | ); |
36 | |
37 | sub dump { |
38 | my $self = shift; |
39 | |
ce444596 |
40 | my $meta = $self->meta; |
41 | |
c79239a2 |
42 | my $dump = ''; |
43 | |
ce444596 |
44 | for my $attribute ( map { $meta->get_attribute($_) } |
45 | sort $meta->get_attribute_list ) { |
6a7e3999 |
46 | |
6a7e3999 |
47 | if ( $attribute->does('MyApp::Meta::Attribute::Trait::Labeled') |
48 | && $attribute->has_label ) { |
c79239a2 |
49 | $dump .= $attribute->label; |
6a7e3999 |
50 | } |
6a7e3999 |
51 | else { |
ce444596 |
52 | $dump .= $attribute->name; |
6a7e3999 |
53 | } |
54 | |
6a7e3999 |
55 | my $reader = $attribute->get_read_method; |
c79239a2 |
56 | $dump .= ": " . $self->$reader . "\n"; |
6a7e3999 |
57 | } |
c79239a2 |
58 | |
59 | return $dump; |
6a7e3999 |
60 | } |
61 | |
62 | package main; |
c79239a2 |
63 | |
6a7e3999 |
64 | my $app = MyApp::Website->new( url => "http://google.com", name => "Google" ); |
aff0421c |
65 | |
66 | =head1 BUT FIRST |
67 | |
4515e88e |
68 | This recipe is a variation on |
09412052 |
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 |
4515e88e |
74 | metaclass which lets you provide a label for attributes. |
75 | |
19320607 |
76 | Using a metaclass works fine until you realize you want to add a label |
4515e88e |
77 | I<and> an expiration, or some other combination of new behaviors. You |
78 | could create yet another metaclass which subclasses those two, but |
79 | that makes a mess, especially if you want to mix and match behaviors |
80 | across many attributes. |
81 | |
82 | Fortunately, Moose provides a much saner alternative, which is to |
83 | encapsulate each extension as a role, not a class. We can make a role |
84 | which adds a label to an attribute, and could make another to |
85 | implement expiration. |
aff0421c |
86 | |
87 | =head1 TRAITS |
88 | |
4515e88e |
89 | Roles that apply to metaclasses have a special name: traits. Don't let |
90 | the change in nomenclature fool you, B<traits are just roles>. |
aff0421c |
91 | |
4515e88e |
92 | L<Moose/has> allows you to pass a C<traits> parameter for an |
93 | attribute. This parameter takes a list of trait names which are |
94 | composed into an anonymous metaclass, and that anonymous metaclass is |
95 | used for the attribute. |
aff0421c |
96 | |
4515e88e |
97 | Yes, we still have lots of metaclasses in the background, but they're |
98 | managed by Moose for you. |
99 | |
100 | Traits can do anything roles can do. They can add or refine |
101 | attributes, wrap methods, provide more methods, define an interface, |
102 | etc. The only difference is that you're now changing the attribute |
103 | metaclass instead of a user-level class. |
aff0421c |
104 | |
105 | =head1 DISSECTION |
106 | |
4515e88e |
107 | A side-by-side look of the code examples in this recipe and recipe 2 |
108 | show that defining and using a trait is very similar to a full-blown |
109 | metaclass. |
aff0421c |
110 | |
6a7e3999 |
111 | package MyApp::Meta::Attribute::Trait::Labeled; |
112 | use Moose::Role; |
aff0421c |
113 | |
6a7e3999 |
114 | has label => ( |
115 | is => 'rw', |
116 | isa => 'Str', |
117 | predicate => 'has_label', |
118 | ); |
aff0421c |
119 | |
4515e88e |
120 | Instead of subclassing L<Moose::Meta::Attribute>, we define a role. As |
121 | with our metaclass in L<recipe 2|Moose::Cookbook::Meta::Recipe2>, |
122 | registering our role allows us to refer to it by a short name. |
aff0421c |
123 | |
6a7e3999 |
124 | package Moose::Meta::Attribute::Custom::Trait::Labeled; |
125 | sub register_implementation { 'MyApp::Meta::Attribute::Trait::Labeled' } |
aff0421c |
126 | |
4515e88e |
127 | Moose looks for the C<register_implementation> method in |
aff0421c |
128 | C<Moose::Meta::Attribute::Custom::Trait::$TRAIT_NAME> to find the full |
129 | name of the trait. |
130 | |
4515e88e |
131 | For the rest of the code, we will only cover what is I<different> from |
132 | L<recipe 2|Moose::Cookbook::Meta::Recipe2>. |
aff0421c |
133 | |
6a7e3999 |
134 | has url => ( |
135 | traits => [qw/Labeled/], |
136 | is => 'rw', |
137 | isa => 'Str', |
138 | label => "The site's URL", |
139 | ); |
aff0421c |
140 | |
4515e88e |
141 | Instead of passing a C<metaclass> parameter, this time we pass |
142 | C<traits>. This contains a list of trait names. Moose will build an |
143 | anonymous attribute metaclass from these traits and use it for this |
144 | attribute. Passing a C<label> parameter works just as it did with the |
145 | metaclass example. |
aff0421c |
146 | |
c79239a2 |
147 | if ( $attribute->does('MyApp::Meta::Attribute::Trait::Labeled') |
148 | && $attribute->has_label ) { |
149 | $dump .= $attribute->label; |
150 | } |
aff0421c |
151 | |
4515e88e |
152 | In the metaclass example, we used C<< $attribute->isa >>. With a role, |
153 | we instead ask if the meta-attribute object C<does> the required |
154 | role. If it does not do this role, the attribute meta object won't |
155 | have the C<has_label> method. |
aff0421c |
156 | |
157 | That's all. Everything else is the same! |
158 | |
4515e88e |
159 | =head1 TURNING A METACLASS INTO A TRAIT |
d9a8643f |
160 | |
161 | "But wait!" you protest. "I've already written all of my extensions as |
162 | attribute metaclasses. I don't want to break all that code out there." |
163 | |
4515e88e |
164 | Fortunately, you can easily turn a metaclass into a trait and still |
165 | provide the original metaclass: |
d9a8643f |
166 | |
6a7e3999 |
167 | package MyApp::Meta::Attribute::Labeled; |
168 | use Moose; |
169 | extends 'Moose::Meta::Attribute'; |
170 | with 'MyApp::Meta::Attribute::Trait::Labeled'; |
d9a8643f |
171 | |
6a7e3999 |
172 | package Moose::Meta::Attribute::Custom::Labeled; |
173 | sub register_implementation { 'MyApp::Meta::Attribute::Labeled' } |
d9a8643f |
174 | |
4515e88e |
175 | Unfortunately, going the other way (providing a trait created from a |
176 | metaclass) is more tricky. |
d9a8643f |
177 | |
aff0421c |
178 | =head1 CONCLUSION |
179 | |
4515e88e |
180 | If you're extending your attributes, it's easier and more flexible to |
181 | provide composable bits of behavior than to subclass |
182 | L<Moose::Meta::Attribute>. Using traits lets you cooperate with other |
183 | extensions, either from CPAN or that you might write in the |
184 | future. Moose makes it easy to create attribute metaclasses on the fly |
185 | by providing a list of trait names to L<Moose/has>. |
aff0421c |
186 | |
187 | =head1 AUTHOR |
188 | |
189 | Shawn M Moore E<lt>sartak@gmail.comE<gt> |
190 | |
4515e88e |
191 | Dave Rolsky E<lt>autarch@urth.org<gt> |
192 | |
aff0421c |
193 | =head1 COPYRIGHT AND LICENSE |
194 | |
7e0492d3 |
195 | Copyright 2006-2010 by Infinity Interactive, Inc. |
aff0421c |
196 | |
197 | L<http://www.iinteractive.com> |
198 | |
199 | This library is free software; you can redistribute it and/or modify |
200 | it under the same terms as Perl itself. |
201 | |
c79239a2 |
202 | =begin testing |
aff0421c |
203 | |
c79239a2 |
204 | my $app2 |
205 | = MyApp::Website->new( url => "http://google.com", name => "Google" ); |
206 | is( |
207 | $app2->dump, q{name: Google |
208 | The site's URL: http://google.com |
209 | }, '... got the expected dump value' |
210 | ); |
aff0421c |
211 | |
c79239a2 |
212 | |
213 | =end testing |
214 | |
215 | =cut |