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