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