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