fixed the test for method modifiers
[gitmo/Moose.git] / lib / Moose / Cookbook / Recipe21.pod
CommitLineData
1edfdf1c 1
2=pod
3
8323a774 4=head1 NAME
5
09912d6a 6Moose::Cookbook::Recipe21 - The meta-attribute example
8323a774 7
8d8832a4 8=head1 SYNOPSIS
9
8323a774 10 package MyApp::Meta::Attribute::Labeled;
11 use Moose;
12 extends 'Moose::Meta::Attribute';
13
05ddeca1 14 has label => (
94acbcd7 15 is => 'rw',
05ddeca1 16 isa => 'Str',
8323a774 17 predicate => 'has_label',
05ddeca1 18 );
8323a774 19
20 package Moose::Meta::Attribute::Custom::Labeled;
21 sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
22
23 package MyApp::Website;
24 use Moose;
25 use MyApp::Meta::Attribute::Labeled;
26
27 has url => (
28 metaclass => 'Labeled',
29 isa => 'Str',
30 is => 'rw',
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 # iterate over all the attributes in $self
43 my %attributes = %{ $self->meta->get_attribute_map };
0cd28189 44 while (my ($name, $attribute) = each %attributes) {
8323a774 45
46 # print the label if available
0cd28189 47 if ($attribute->isa('MyApp::Meta::Attribute::Labeled')
48 && $attribute->has_label) {
49 print $attribute->label;
8323a774 50 }
51 # otherwise print the name
52 else {
53 print $name;
54 }
55
56 # print the attribute's value
0cd28189 57 my $reader = $attribute->get_read_method;
8323a774 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 SUMMARY
67
68In this recipe, we begin to really delve into the wonder of meta-programming.
69Some readers may scoff and claim that this is the arena only of the most
70twisted Moose developers. Absolutely not! Any sufficiently twisted developer
71can benefit greatly from going more meta.
72
73The high-level goal of this recipe's code is to allow each attribute to have a
74human-readable "label" attached to it. Such labels would be used when showing
75data to an end user. In this recipe we label the "url" attribute with "The
76site's URL" and create a simple method to demonstrate how to use that label.
77
78=head1 REAL ATTRIBUTES 101
79
80All the attributes of a Moose-based object are actually objects themselves.
81These objects have methods and (surprisingly) attributes. Let's look at a
82concrete example.
83
84 has 'x' => (isa => 'Int', is => 'ro');
85 has 'y' => (isa => 'Int', is => 'rw');
86
87Ahh, the veritable x and y of the Point example. Internally, every Point has an
88x object and a y object. They have methods (such as "get_value") and attributes
89(such as "is_lazy"). What class are they instances of?
90L<Moose::Meta::Attribute>. You don't normally see the objects lurking behind
91the scenes, because you usually just use C<< $point->x >> and C<< $point->y >>
92and forget that there's a lot of machinery lying in such methods.
93
94So you have a C<$point> object, which has C<x> and C<y> methods. How can you
95actually access the objects behind these attributes? Here's one way:
96
97 $point->meta->get_attribute_map()
98
99C<get_attribute_map> returns a hash reference that maps attribute names to
100their objects. In our case, C<get_attribute_map> might return something that
101looks like the following:
102
103 {
104 x => Moose::Meta::Attribute=HASH(0x196c23c),
105 y => Moose::Meta::Attribute=HASH(0x18d1690),
106 }
107
376a56f7 108Another way to get a handle on an attribute's object is
109C<< $self->meta->get_attribute('name') >>. Here's one thing you can do now that
110you can interact with the attribute's object directly:
8323a774 111
376a56f7 112 print $point->meta->get_attribute('x')->type_constraint;
113 => Int
8323a774 114
115(As an aside, it's not called C<< ->isa >> because C<< $obj->isa >> is already
116taken)
117
118So to actually beef up attributes, what we need to do is:
119
120=over 4
121
122=item Create a new attribute metaclass
123
124=item Create attributes using that new metaclass
125
126=back
127
128Moose makes both of these easy!
129
130Let's start dissecting the recipe's code.
131
132=head1 DISSECTION
133
134We get the ball rolling by creating a new attribute metaclass. It starts off
135somewhat ungloriously.
136
137 package MyApp::Meta::Attribute::Labeled;
138 use Moose;
139 extends 'Moose::Meta::Attribute';
140
141You subclass metaclasses the same way you subclass regular classes. (Extra
94acbcd7 142credit: how in the actual hell can you use the MOP to extend itself?)
8323a774 143
05ddeca1 144 has label => (
94acbcd7 145 is => 'rw',
05ddeca1 146 isa => 'Str',
8323a774 147 predicate => 'has_label',
05ddeca1 148 );
8323a774 149
94acbcd7 150Hey, this looks pretty reasonable! This is plain jane Moose code. Recipe 1
151fare. This is merely making a new attribute. An attribute that attributes have.
152A meta-attribute. It may sound scary, but it really isn't! Reread
153L<REAL ATTRIBUTES 101> if this really is terrifying.
8323a774 154
fe2d5aba 155The name is "label", it will have a regular accessor, and is a string.
156C<predicate> is a standard part of C<has>. It just creates a method that asks
157the question "Does this attribute have a value?"
8323a774 158
159 package Moose::Meta::Attribute::Custom::Labeled;
160 sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
161
fe2d5aba 162This lets Moose discover our new metaclass. That way attributes can actually
8323a774 163use it. More on what this is doing in a moment.
164
165Note that we're done defining the new metaclass! Only nine lines of code, and
166not particularly difficult lines, either. Now to start using the metaclass.
167
168 package MyApp::Website;
169 use Moose;
170 use MyApp::Meta::Attribute::Labeled;
171
172Nothing new here. We do have to actually load our metaclass to be able to use
173it.
174
175 has url => (
176 metaclass => 'Labeled',
177 isa => 'Str',
178 is => 'rw',
179 label => "The site's URL",
180 );
181
182Ah ha! Now we're using the metaclass. We're adding a new attribute, C<url>, to
dbea36a1 183C<MyApp::Website>. C<has> lets you set the metaclass of the attribute.
184Ordinarily (as we've seen), the metaclass is C<Moose::Meta::Attribute>.
8323a774 185
186When C<has> sees that you're using a new metaclass, it will take the
187metaclass's name, prepend C<Moose::Meta::Attribute::Custom::>, and call the
188C<register_implementation> function in that package. So here Moose calls
dbea36a1 189C<Moose::Meta::Attribute::Custom::Labeled::register_implementation>. We defined
190that function in the beginning -- it just returns our "real" metaclass'
191package, C<MyApp::Meta::Attribute::Labeled>. So Moose uses that metaclass for
192the attribute. It may seem a bit convoluted, but the alternative would be to
193use C<< metaclass => 'MyApp::Meta::Attribute::Labeled' >> on every attribute.
194As usual, Moose optimizes in favor of the end user, not the metaprogrammer. :)
fe2d5aba 195We also could have just defined the metaclass in
196C<Moose::Meta::Attribute::Custom::Labeled>, but it's probably better to keep to
197your own namespaces.
8323a774 198
199Finally, we see that C<has> is setting our new meta-attribute, C<label>, to
94acbcd7 200C<"The site's URL">. We can access this meta-attribute with:
201
202 $website->meta->get_attribute('url')->label()
203
fe2d5aba 204Well, back to the code.
8323a774 205
206 has name => (
207 is => 'rw',
208 isa => 'Str',
209 );
210
fe2d5aba 211Of course, you don't have to use the new metaclass for B<all> new attributes.
8323a774 212
213Now we begin defining a method that will dump the C<MyApp::Website> instance
214for human readers.
215
216 sub dump {
217 my $self = shift;
218
219 # iterate over all the attributes in $self
220 my %attributes = %{ $self->meta->get_attribute_map };
0cd28189 221 while (my ($name, $attribute) = each %attributes) {
8323a774 222
94acbcd7 223Recall that C<get_attribute_map> returns a hashref of attribute names and their
224associated objects.
8323a774 225
226 # print the label if available
0cd28189 227 if ($attribute->isa('MyApp::Meta::Attribute::Labeled')
228 && $attribute->has_label) {
229 print $attribute->label;
8323a774 230 }
231
94acbcd7 232We have two checks here. The first is "is this attribute an instance of
233C<MyApp::Meta::Attribute::Labeled>?". It's good to code defensively. Even if
234all of your attributes have this metaclass, you never know when someone is
fe2d5aba 235going to subclass your work of art. Poorly. In other words, it's likely that
236there will still be (many) attributes that are instances of the default
237C<Moose::Meta::Attribute>.
238
239The second check is "does this attribute have a label?". This method was
240defined in the new metaclass as the "predicate". If we pass both checks, we
241print the attribute's label.
8323a774 242
243 # otherwise print the name
244 else {
245 print $name;
246 }
247
248Another good, defensive coding practice: Provide reasonable defaults.
249
250 # print the attribute's value
0cd28189 251 my $reader = $attribute->get_read_method;
8323a774 252 print ": " . $self->$reader . "\n";
253 }
254 }
255
94acbcd7 256Here's another example of using the attribute metaclass.
257C<< $attribute->get_read_method >> returns the name of the method that can
fe2d5aba 258be invoked on the original object to read the attribute's value.
94acbcd7 259C<< $self->$reader >> is an example of "reflection" -- instead of using the
260name of the method, we're using a variable with the name of the method in it.
261Perl doesn't mind. Another way to write this would be
fe2d5aba 262C<< $self->can($reader)->($self) >>. Yuck. :)
8323a774 263
264 package main;
265 my $app = MyApp::Website->new(url => "http://google.com", name => "Google");
266 $app->dump;
267
fe2d5aba 268And we wrap up the example with a script to show off our newfound magic.
8323a774 269
270=head1 CONCLUSION
271
272Why oh why would you want to go through all of these contortions when you can
273just print "The site's URL" directly in the C<dump> method? For one, the DRY
274(Don't Repeat Yourself) principle. If you have it in the C<dump> method, you'll
275probably also have it in the C<as_form> method, and C<to_file>, and so on. So
276why not have a method that maps attribute names to labels? That could work, but
277why not include the label where it belongs, in the attribute's definition?
278That way you're also less likely to forget to add the label.
279
280More importantly, this was a very simple example. Your metaclasses aren't
281limited to just adding new meta-attributes. For example, you could implement
fe2d5aba 282a metaclass that expires attributes after a certain amount of time. You
283might use it as such:
8323a774 284
285 has site_cache => (
fe2d5aba 286 metaclass => 'TimedExpiry',
287 expires_after => { hours => 1 },
288 refresh_with => sub { get($_->url) },
289 isa => 'Str',
290 is => 'ro',
8323a774 291 );
292
293The sky's the limit!
294
295=head1 AUTHOR
296
297Shawn M Moore E<lt>sartak@gmail.comE<gt>
298
299=head1 COPYRIGHT AND LICENSE
300
778db3ac 301Copyright 2006-2008 by Infinity Interactive, Inc.
8323a774 302
303L<http://www.iinteractive.com>
304
305This library is free software; you can redistribute it and/or modify
306it under the same terms as Perl itself.
307
308=cut
309