Revised recipe 2
[gitmo/Moose.git] / lib / Moose / Cookbook / Meta / Recipe3.pod
CommitLineData
aff0421c 1
2=pod
3
4=head1 NAME
5
43aa5bf9 6Moose::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 68This recipe is a continuation of
69L<Moose::Cookbook::Meta::Recipe2>. Please read that recipe first.
aff0421c 70
71=head1 MOTIVATION
72
09412052 73In L<Moose::Cookbook::Meta::Recipe2>, we created an attribute
74metaclass that gives attributes a "label" that can be set in
75L<Moose/has>. That works well until you want a second meta-attribute,
76or until you want to adjust the behavior of the attribute. You could
77define a specialized attribute metaclass to use in every attribute.
78However, you may want different attributes to have different
79behaviors. You might end up with a unique attribute metaclass for
80B<every single attribute>, with a lot of code copying and pasting!
aff0421c 81
82Or, if you've been drinking deeply of the Moose kool-aid, you'll have a role
83for each of the behaviors. One role would give a label meta-attribute. Another
84role would signify that this attribute is not directly modifiable via the
85REST interface. Another role would write to a logfile when this attribute
86was read.
87
88Unfortunately, you'd still be left with a bunch of attribute metaclasses that
89do nothing but compose a bunch of roles. If only there were some way to specify
90in L<Moose/has> a list of roles to apply to the attribute metaclass...
91
92=head1 TRAITS
93
94Roles that apply to metaclasses have a special name: traits. Don't let the
95change in nomenclature fool you, B<traits are just roles>.
96
97L<Moose/has> provides a C<traits> option. It takes a list of trait names to
433b5928 98compose into an anonymous metaclass. That means you do still have a bunch of
99attribute metaclasses that do nothing but compose a bunch of roles, but they're
100managed automatically by Moose. You don't need to declare them in advance, or
101worry whether changing one will affect some other attribute.
aff0421c 102
103What can traits do? Anything roles can do. They can add or refine attributes,
104wrap methods, provide more methods, define an interface, etc. The only
105difference is that you're now changing the attribute metaclass instead of a
106user-level class.
107
108=head1 DISSECTION
109
09412052 110A side-by-side look of the code examples in this recipe and recipe 2 should
aff0421c 111indicate that defining and using a trait is very similar to defining and using
433b5928 112a 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
123Instead of subclassing L<Moose::Meta::Attribute>, we define a role. Traits
124don't need any special methods or attributes. You just focus on whatever it is
125you actually need to get done. Here we're adding a new meta-attribute for use
126in our application.
127
6a7e3999 128 package Moose::Meta::Attribute::Custom::Trait::Labeled;
129 sub register_implementation { 'MyApp::Meta::Attribute::Trait::Labeled' }
aff0421c 130
433b5928 131Much like when we define a new attribute metaclass, we can provide a shorthand
aff0421c 132name for the trait. Moose looks at the C<register_implementation> method in
133C<Moose::Meta::Attribute::Custom::Trait::$TRAIT_NAME> to find the full
134name of the trait.
135
136Now we begin writing our application logic. I'll only cover what has changed
09412052 137since 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
146L<Moose/has> provides a C<traits> option. Just pass the list of trait names and
147it will compose them together to form the (anonymous) attribute metaclass used
433b5928 148by 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 156Previously, this code asked the question "Does this attribute use our attribute
157metaclass?" Since we're now using a trait, we ask "Does this attribute's
158metaclass do the C<Labeled> role?" If not, the attribute metaclass won't have
159the C<has_label> method, and so it would be an error to blindly call
aff0421c 160C<< $attribute->has_label >>.
161
162That'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
167attribute metaclasses. I don't want to break all that code out there."
168
169All is not lost. If you rewrite your extension as a trait, then you can
170easily get a regular metaclass extension out of it. You just compose the trait
171in 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
181Unfortunately, going the other way (providing a trait created from a metaclass)
182is more tricky. Thus, defining your extensions as traits is just plain better
183than defining them as subclassed metaclasses.
184
aff0421c 185=head1 CONCLUSION
186
187If you're extending your attributes, it's easier and more flexible to provide
433b5928 188composable bits of behavior than to subclass L<Moose::Meta::Attribute>.
aff0421c 189Using traits (which are just roles applied to a metaclass!) let you choose
190exactly which behaviors each attribute will have. Moose makes it easy to create
191attribute metaclasses on the fly by providing a list of trait names to
192L<Moose/has>.
193
194=head1 AUTHOR
195
196Shawn M Moore E<lt>sartak@gmail.comE<gt>
197
198=head1 COPYRIGHT AND LICENSE
199
2840a3b2 200Copyright 2006-2009 by Infinity Interactive, Inc.
aff0421c 201
202L<http://www.iinteractive.com>
203
204This library is free software; you can redistribute it and/or modify
205it under the same terms as Perl itself.
206
207=cut
208
209