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