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