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