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