Wow, you really can use 'has' to create new meta-attributes. stevan++!
[gitmo/Moose.git] / lib / Moose / Cookbook / Recipe11.pod
CommitLineData
8323a774 1
2=pod
3
4=head1 NAME
5
6Moose::Cookbook::Recipe11 - The meta-attribute example
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 => (
15 is => 'ro',
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 };
44 while (my ($name, $meta_attribute) = each %attributes) {
45
46 # print the label if available
47 if ($meta_attribute->isa('MyApp::Meta::Attribute::Labeled')
48 && $meta_attribute->has_label) {
49 print $meta_attribute->label;
50 }
51 # otherwise print the name
52 else {
53 print $name;
54 }
55
56 # print the attribute's value
57 my $reader = $meta_attribute->get_read_method;
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
108Here's one thing you can do now that you can interact with the attribute's
109object directly:
110
111 print $point->meta->get_attribute_map->{x}->type_constraint;
112 => Int
113
114(As an aside, it's not called C<< ->isa >> because C<< $obj->isa >> is already
115taken)
116
117So to actually beef up attributes, what we need to do is:
118
119=over 4
120
121=item Create a new attribute metaclass
122
123=item Create attributes using that new metaclass
124
125=back
126
127Moose makes both of these easy!
128
129Let's start dissecting the recipe's code.
130
131=head1 DISSECTION
132
133We get the ball rolling by creating a new attribute metaclass. It starts off
134somewhat ungloriously.
135
136 package MyApp::Meta::Attribute::Labeled;
137 use Moose;
138 extends 'Moose::Meta::Attribute';
139
140You subclass metaclasses the same way you subclass regular classes. (Extra
141credit: how in the actual hell can you use the MOP to extend itself?) Moving
142on.
143
05ddeca1 144 has label => (
145 is => 'ro',
146 isa => 'Str',
8323a774 147 predicate => 'has_label',
05ddeca1 148 );
8323a774 149
150Now things get a little icky. We're adding a attribute to the attribute
151metaclass. For clarity, I'm going to call this a meta-attribute.
152
05ddeca1 153This creates a new meta-attribute in the C<MyApp::Meta::Attribute::Labeled>
154metaclass. The new meta-attribute's name is 'label'. The predicate just creates
155a method that asks the question "Does this attribute have a value?"
8323a774 156
05ddeca1 157Of course, if you step a foot back, you can see that this is really just adding
158an attribute to a class. Don't be alarmed!
8323a774 159
160 package Moose::Meta::Attribute::Custom::Labeled;
161 sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
162
163This registers the new metaclass with Moose. That way attributes can actually
164use it. More on what this is doing in a moment.
165
166Note that we're done defining the new metaclass! Only nine lines of code, and
167not particularly difficult lines, either. Now to start using the metaclass.
168
169 package MyApp::Website;
170 use Moose;
171 use MyApp::Meta::Attribute::Labeled;
172
173Nothing new here. We do have to actually load our metaclass to be able to use
174it.
175
176 has url => (
177 metaclass => 'Labeled',
178 isa => 'Str',
179 is => 'rw',
180 label => "The site's URL",
181 );
182
183Ah ha! Now we're using the metaclass. We're adding a new attribute, C<url>, to
dbea36a1 184C<MyApp::Website>. C<has> lets you set the metaclass of the attribute.
185Ordinarily (as we've seen), the metaclass is C<Moose::Meta::Attribute>.
8323a774 186
187When C<has> sees that you're using a new metaclass, it will take the
188metaclass's name, prepend C<Moose::Meta::Attribute::Custom::>, and call the
189C<register_implementation> function in that package. So here Moose calls
dbea36a1 190C<Moose::Meta::Attribute::Custom::Labeled::register_implementation>. We defined
191that function in the beginning -- it just returns our "real" metaclass'
192package, C<MyApp::Meta::Attribute::Labeled>. So Moose uses that metaclass for
193the attribute. It may seem a bit convoluted, but the alternative would be to
194use C<< metaclass => 'MyApp::Meta::Attribute::Labeled' >> on every attribute.
195As usual, Moose optimizes in favor of the end user, not the metaprogrammer. :)
8323a774 196
197Finally, we see that C<has> is setting our new meta-attribute, C<label>, to
198C<"The site's URL">.
199
200 has name => (
201 is => 'rw',
202 isa => 'Str',
203 );
204
205You do not of course need to use the new metaclass for all new attributes.
206
207Now we begin defining a method that will dump the C<MyApp::Website> instance
208for human readers.
209
210 sub dump {
211 my $self = shift;
212
213 # iterate over all the attributes in $self
214 my %attributes = %{ $self->meta->get_attribute_map };
215 while (my ($name, $meta_attribute) = each %attributes) {
216
217We covered the latter two lines of code earlier.
218
219 # print the label if available
220 if ($meta_attribute->isa('MyApp::Meta::Attribute::Labeled')
221 && $meta_attribute->has_label) {
222 print $meta_attribute->label;
223 }
224
225Note that we have two checks here. The first is "is this attribute an instance
226of C<MyApp::Meta::Attribute::Labeled>?". It's good to code defensively, even if
227all of your attributes have this metaclass. You never know when someone is
228going to subclass your work of art, poorly. The second check is "does this
229attribute have a label?". This method was defined in the new metaclass as the
dbea36a1 230"predicate". If we pass both checks, we print the attribute's label. The
231C<< ->label >> method was defined in the new metaclass as the "reader".
8323a774 232
233 # otherwise print the name
234 else {
235 print $name;
236 }
237
238Another good, defensive coding practice: Provide reasonable defaults.
239
240 # print the attribute's value
241 my $reader = $meta_attribute->get_read_method;
242 print ": " . $self->$reader . "\n";
243 }
244 }
245
dbea36a1 246Here's another example of using the attribute metaclass. C<<
247$meta_attribute->get_read_method >> returns the name of the method that can
248invoked on the original object to read the attribute's value. C<<
249$self->$reader >> is an example of "reflection". Instead of using the name of
250the method, we're using a variable with the name of the method in it. Perl
251doesn't mind. Another way to write this would be
252C<< $self->can($reader)->() >>.
8323a774 253
254 package main;
255 my $app = MyApp::Website->new(url => "http://google.com", name => "Google");
256 $app->dump;
257
258And finish off the example with a script to show off our newfound magic.
259
260=head1 CONCLUSION
261
262Why oh why would you want to go through all of these contortions when you can
263just print "The site's URL" directly in the C<dump> method? For one, the DRY
264(Don't Repeat Yourself) principle. If you have it in the C<dump> method, you'll
265probably also have it in the C<as_form> method, and C<to_file>, and so on. So
266why not have a method that maps attribute names to labels? That could work, but
267why not include the label where it belongs, in the attribute's definition?
268That way you're also less likely to forget to add the label.
269
270More importantly, this was a very simple example. Your metaclasses aren't
271limited to just adding new meta-attributes. For example, you could implement
272a metaclass that expires attributes after a certain amount of time.
273
274 has site_cache => (
275 metaclass => 'Expiry',
276 expires_after => '1 hour',
12369f85 277 refresh_with => sub { my $self = shift; get($self->url) },
8323a774 278 isa => 'Str',
279 );
280
281The sky's the limit!
282
283=head1 AUTHOR
284
285Shawn M Moore E<lt>sartak@gmail.comE<gt>
286
287=head1 COPYRIGHT AND LICENSE
288
289Copyright 2006, 2007 by Infinity Interactive, Inc.
290
291L<http://www.iinteractive.com>
292
293This library is free software; you can redistribute it and/or modify
294it under the same terms as Perl itself.
295
296=cut
297
2981;
299