Revised recipe 3
[gitmo/Moose.git] / lib / Moose / Cookbook / Meta / Recipe3.pod
1
2 =pod
3
4 =head1 NAME
5
6 Moose::Cookbook::Meta::Recipe3 - Labels implemented via attribute traits
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/],
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;
65
66 =head1 BUT FIRST
67
68 This recipe is a variation on
69 L<Moose::Cookbook::Meta::Recipe2>. Please read that recipe first.
70
71 =head1 MOTIVATION
72
73 In L<Moose::Cookbook::Meta::Recipe2>, we created an attribute
74 metaclass which lets you provide a label for attributes.
75
76 Using a metaclss works fine until you realize you want to add a label
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.
86
87 =head1 TRAITS
88
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>.
91
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.
96
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.
104
105 =head1 DISSECTION
106
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.
110
111   package MyApp::Meta::Attribute::Trait::Labeled;
112   use Moose::Role;
113
114   has label => (
115       is        => 'rw',
116       isa       => 'Str',
117       predicate => 'has_label',
118   );
119
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.
123
124   package Moose::Meta::Attribute::Custom::Trait::Labeled;
125   sub register_implementation { 'MyApp::Meta::Attribute::Trait::Labeled' }
126
127 Moose looks for the C<register_implementation> method in
128 C<Moose::Meta::Attribute::Custom::Trait::$TRAIT_NAME> to find the full
129 name of the trait.
130
131 For the rest of the code, we will only cover what is I<different> from
132 L<recipe 2|Moose::Cookbook::Meta::Recipe2>.
133
134   has url => (
135       traits => [qw/Labeled/],
136       is     => 'rw',
137       isa    => 'Str',
138       label  => "The site's URL",
139   );
140
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.
146
147   # print the label if available
148   if (   $attribute->does('MyApp::Meta::Attribute::Trait::Labeled')
149       && $attribute->has_label ) {
150       print $attribute->label;
151   }
152
153 In the metaclass example, we used C<< $attribute->isa >>. With a role,
154 we instead ask if the meta-attribute object C<does> the required
155 role. If it does not do this role, the attribute meta object won't
156 have the C<has_label> method.
157
158 That's all. Everything else is the same!
159
160 =head1 TURNING A METACLASS INTO A TRAIT
161
162 "But wait!" you protest. "I've already written all of my extensions as
163 attribute metaclasses. I don't want to break all that code out there."
164
165 Fortunately, you can easily turn a metaclass into a trait and still
166 provide the original metaclass:
167
168   package MyApp::Meta::Attribute::Labeled;
169   use Moose;
170   extends 'Moose::Meta::Attribute';
171   with 'MyApp::Meta::Attribute::Trait::Labeled';
172
173   package Moose::Meta::Attribute::Custom::Labeled;
174   sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
175
176 Unfortunately, going the other way (providing a trait created from a
177 metaclass) is more tricky.
178
179 =head1 CONCLUSION
180
181 If you're extending your attributes, it's easier and more flexible to
182 provide composable bits of behavior than to subclass
183 L<Moose::Meta::Attribute>. Using traits lets you cooperate with other
184 extensions, either from CPAN or that you might write in the
185 future. Moose makes it easy to create attribute metaclasses on the fly
186 by providing a list of trait names to L<Moose/has>.
187
188 =head1 AUTHOR
189
190 Shawn M Moore E<lt>sartak@gmail.comE<gt>
191
192 Dave Rolsky E<lt>autarch@urth.org<gt>
193
194 =head1 COPYRIGHT AND LICENSE
195
196 Copyright 2006-2009 by Infinity Interactive, Inc.
197
198 L<http://www.iinteractive.com>
199
200 This library is free software; you can redistribute it and/or modify
201 it under the same terms as Perl itself.
202
203 =cut
204
205