6 Moose::Cookbook::Recipe11 - The meta-attribute example
8 package MyApp::Meta::Attribute::Labeled;
10 extends 'Moose::Meta::Attribute';
12 __PACKAGE__->meta->add_attribute('label' => (
14 predicate => 'has_label',
17 package Moose::Meta::Attribute::Custom::Labeled;
18 sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
20 package MyApp::Website;
22 use MyApp::Meta::Attribute::Labeled;
25 metaclass => 'Labeled',
28 label => "The site's URL",
39 # iterate over all the attributes in $self
40 my %attributes = %{ $self->meta->get_attribute_map };
41 while (my ($name, $meta_attribute) = each %attributes) {
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;
48 # otherwise print the name
53 # print the attribute's value
54 my $reader = $meta_attribute->get_read_method;
55 print ": " . $self->$reader . "\n";
60 my $app = MyApp::Website->new(url => "http://google.com", name => "Google");
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.
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.
75 =head1 REAL ATTRIBUTES 101
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
81 has 'x' => (isa => 'Int', is => 'ro');
82 has 'y' => (isa => 'Int', is => 'rw');
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.
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:
94 $point->meta->get_attribute_map()
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:
101 x => Moose::Meta::Attribute=HASH(0x196c23c),
102 y => Moose::Meta::Attribute=HASH(0x18d1690),
105 Here's one thing you can do now that you can interact with the attribute's
108 print $point->meta->get_attribute_map->{x}->type_constraint;
111 (As an aside, it's not called C<< ->isa >> because C<< $obj->isa >> is already
114 So to actually beef up attributes, what we need to do is:
118 =item Create a new attribute metaclass
120 =item Create attributes using that new metaclass
124 Moose makes both of these easy!
126 Let's start dissecting the recipe's code.
130 We get the ball rolling by creating a new attribute metaclass. It starts off
131 somewhat ungloriously.
133 package MyApp::Meta::Attribute::Labeled;
135 extends 'Moose::Meta::Attribute';
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
141 __PACKAGE__->meta->add_attribute('label' => (
143 predicate => 'has_label',
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.
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?"
155 Note the resemblance between C<add_attribute> and C<has>. C<has> actually just
156 uses C<add_attribute> behind the scenes.
158 package Moose::Meta::Attribute::Custom::Labeled;
159 sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
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.
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.
167 package MyApp::Website;
169 use MyApp::Meta::Attribute::Labeled;
171 Nothing new here. We do have to actually load our metaclass to be able to use
175 metaclass => 'Labeled',
178 label => "The site's URL",
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>.
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. :)
194 Finally, we see that C<has> is setting our new meta-attribute, C<label>, to
202 You do not of course need to use the new metaclass for all new attributes.
204 Now we begin defining a method that will dump the C<MyApp::Website> instance
210 # iterate over all the attributes in $self
211 my %attributes = %{ $self->meta->get_attribute_map };
212 while (my ($name, $meta_attribute) = each %attributes) {
214 We covered the latter two lines of code earlier.
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;
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
229 # otherwise print the name
234 Another good, defensive coding practice: Provide reasonable defaults.
236 # print the attribute's value
237 my $reader = $meta_attribute->get_read_method;
238 print ": " . $self->$reader . "\n";
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)->() >>.
249 my $app = MyApp::Website->new(url => "http://google.com", name => "Google");
252 And finish off the example with a script to show off our newfound magic.
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.
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.
269 metaclass => 'Expiry',
270 expires_after => '1 hour',
271 refresh_with => sub { ... },
279 Shawn M Moore E<lt>sartak@gmail.comE<gt>
281 =head1 COPYRIGHT AND LICENSE
283 Copyright 2006, 2007 by Infinity Interactive, Inc.
285 L<http://www.iinteractive.com>
287 This library is free software; you can redistribute it and/or modify
288 it under the same terms as Perl itself.