985ce5f1b77549a1fd09dda3907f098c93263078
[gitmo/Moose.git] / lib / Moose / Cookbook / Meta / Recipe3.pod
1 package Moose::Cookbook::Meta::Recipe3;
2
3 # ABSTRACT: Labels implemented via attribute traits
4
5 __END__
6
7
8 =pod
9
10 =head1 SYNOPSIS
11
12   package MyApp::Meta::Attribute::Trait::Labeled;
13   use Moose::Role;
14
15   has label => (
16       is        => 'rw',
17       isa       => 'Str',
18       predicate => 'has_label',
19   );
20
21   package Moose::Meta::Attribute::Custom::Trait::Labeled;
22   sub register_implementation {'MyApp::Meta::Attribute::Trait::Labeled'}
23
24   package MyApp::Website;
25   use Moose;
26
27   has url => (
28       traits => [qw/Labeled/],
29       is     => 'rw',
30       isa    => 'Str',
31       label  => "The site's URL",
32   );
33
34   has name => (
35       is  => 'rw',
36       isa => 'Str',
37   );
38
39   sub dump {
40       my $self = shift;
41
42       my $meta = $self->meta;
43
44       my $dump = '';
45
46       for my $attribute ( map { $meta->get_attribute($_) }
47           sort $meta->get_attribute_list ) {
48
49           if (   $attribute->does('MyApp::Meta::Attribute::Trait::Labeled')
50               && $attribute->has_label ) {
51               $dump .= $attribute->label;
52           }
53           else {
54               $dump .= $attribute->name;
55           }
56
57           my $reader = $attribute->get_read_method;
58           $dump .= ": " . $self->$reader . "\n";
59       }
60
61       return $dump;
62   }
63
64   package main;
65
66   my $app = MyApp::Website->new( url => "http://google.com", name => "Google" );
67
68 =head1 BUT FIRST
69
70 This recipe is a variation on
71 L<Moose::Cookbook::Meta::Recipe2>. Please read that recipe first.
72
73 =head1 MOTIVATION
74
75 In L<Moose::Cookbook::Meta::Recipe2>, we created an attribute
76 metaclass which lets you provide a label for attributes.
77
78 Using a metaclass works fine until you realize you want to add a label
79 I<and> an expiration, or some other combination of new behaviors. You
80 could create yet another metaclass which subclasses those two, but
81 that makes a mess, especially if you want to mix and match behaviors
82 across many attributes.
83
84 Fortunately, Moose provides a much saner alternative, which is to
85 encapsulate each extension as a role, not a class. We can make a role
86 which adds a label to an attribute, and could make another to
87 implement expiration.
88
89 =head1 TRAITS
90
91 Roles that apply to metaclasses have a special name: traits. Don't let
92 the change in nomenclature fool you, B<traits are just roles>.
93
94 L<Moose/has> allows you to pass a C<traits> parameter for an
95 attribute. This parameter takes a list of trait names which are
96 composed into an anonymous metaclass, and that anonymous metaclass is
97 used for the attribute.
98
99 Yes, we still have lots of metaclasses in the background, but they're
100 managed by Moose for you.
101
102 Traits can do anything roles can do. They can add or refine
103 attributes, wrap methods, provide more methods, define an interface,
104 etc. The only difference is that you're now changing the attribute
105 metaclass instead of a user-level class.
106
107 =head1 DISSECTION
108
109 A side-by-side look of the code examples in this recipe and recipe 2
110 show that defining and using a trait is very similar to a full-blown
111 metaclass.
112
113   package MyApp::Meta::Attribute::Trait::Labeled;
114   use Moose::Role;
115
116   has label => (
117       is        => 'rw',
118       isa       => 'Str',
119       predicate => 'has_label',
120   );
121
122 Instead of subclassing L<Moose::Meta::Attribute>, we define a role. As
123 with our metaclass in L<recipe 2|Moose::Cookbook::Meta::Recipe2>,
124 registering our role allows us to refer to it by a short name.
125
126   package Moose::Meta::Attribute::Custom::Trait::Labeled;
127   sub register_implementation { 'MyApp::Meta::Attribute::Trait::Labeled' }
128
129 Moose looks for the C<register_implementation> method in
130 C<Moose::Meta::Attribute::Custom::Trait::$TRAIT_NAME> to find the full
131 name of the trait.
132
133 For the rest of the code, we will only cover what is I<different> from
134 L<recipe 2|Moose::Cookbook::Meta::Recipe2>.
135
136   has url => (
137       traits => [qw/Labeled/],
138       is     => 'rw',
139       isa    => 'Str',
140       label  => "The site's URL",
141   );
142
143 Instead of passing a C<metaclass> parameter, this time we pass
144 C<traits>. This contains a list of trait names. Moose will build an
145 anonymous attribute metaclass from these traits and use it for this
146 attribute. Passing a C<label> parameter works just as it did with the
147 metaclass example.
148
149           if (   $attribute->does('MyApp::Meta::Attribute::Trait::Labeled')
150               && $attribute->has_label ) {
151               $dump .= $attribute->label;
152           }
153
154 In the metaclass example, we used C<< $attribute->isa >>. With a role,
155 we instead ask if the meta-attribute object C<does> the required
156 role. If it does not do this role, the attribute meta object won't
157 have the C<has_label> method.
158
159 That's all. Everything else is the same!
160
161 =head1 TURNING A METACLASS INTO A TRAIT
162
163 "But wait!" you protest. "I've already written all of my extensions as
164 attribute metaclasses. I don't want to break all that code out there."
165
166 Fortunately, you can easily turn a metaclass into a trait and still
167 provide the original metaclass:
168
169   package MyApp::Meta::Attribute::Labeled;
170   use Moose;
171   extends 'Moose::Meta::Attribute';
172   with 'MyApp::Meta::Attribute::Trait::Labeled';
173
174   package Moose::Meta::Attribute::Custom::Labeled;
175   sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
176
177 Unfortunately, going the other way (providing a trait created from a
178 metaclass) is more tricky.
179
180 =head1 CONCLUSION
181
182 If you're extending your attributes, it's easier and more flexible to
183 provide composable bits of behavior than to subclass
184 L<Moose::Meta::Attribute>. Using traits lets you cooperate with other
185 extensions, either from CPAN or that you might write in the
186 future. Moose makes it easy to create attribute metaclasses on the fly
187 by providing a list of trait names to L<Moose/has>.
188
189 =begin testing
190
191 my $app2
192     = MyApp::Website->new( url => "http://google.com", name => "Google" );
193 is(
194     $app2->dump, q{name: Google
195 The site's URL: http://google.com
196 }, '... got the expected dump value'
197 );
198
199
200 =end testing
201
202 =cut