4c38c19d03059d7de78b60968c2b1bf38b9b7a50
[gitmo/Moose.git] / lib / Moose / Cookbook / Meta / Recipe2.pod
1
2 =pod
3
4 =head1 NAME
5
6 Moose::Cookbook::Meta::Recipe2 - A meta-attribute, attributes with labels
7
8 =head1 SYNOPSIS
9
10   package MyApp::Meta::Attribute::Labeled;
11   use Moose;
12   extends 'Moose::Meta::Attribute';
13
14   has label => (
15       is        => 'rw',
16       isa       => 'Str',
17       predicate => 'has_label',
18   );
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
26   has url => (
27       metaclass => 'Labeled',
28       is        => 'rw',
29       isa       => 'Str',
30       label     => "The site's URL",
31   );
32
33   has name => (
34       is  => 'rw',
35       isa => 'Str',
36   );
37
38   sub dump {
39       my $self = shift;
40
41       my $dump = '';
42
43       my %attributes = %{ $self->meta->get_attribute_map };
44       for my $name ( sort keys %attributes ) {
45           my $attribute = $attributes{$name};
46
47           if (   $attribute->isa('MyApp::Meta::Attribute::Labeled')
48               && $attribute->has_label ) {
49               $dump .= $attribute->label;
50           }
51           else {
52               $dump .= $name;
53           }
54
55           my $reader = $attribute->get_read_method;
56           $dump .= ": " . $self->$reader . "\n";
57       }
58
59       return $dump;
60   }
61
62   package main;
63
64   my $app = MyApp::Website->new( url => "http://google.com", name => "Google" );
65
66 =head1 SUMMARY
67
68 In this recipe, we begin to delve into the wonder of meta-programming.
69 Some readers may scoff and claim that this is the arena of only the
70 most twisted Moose developers. Absolutely not! Any sufficiently
71 twisted developer can benefit greatly from going more meta.
72
73 Our goal is to allow each attribute to have a human-readable "label"
74 attached to it. Such labels would be used when showing data to an end
75 user. In this recipe we label the C<url> attribute with "The site's
76 URL" and create a simple method showing how to use that label.
77
78 =head1 META-ATTRIBUTE OBJECTS
79
80 All the attributes of a Moose-based object are actually objects
81 themselves.  These objects have methods and attributes. Let's look at
82 a concrete example.
83
84   has 'x' => ( isa => 'Int', is => 'ro' );
85   has 'y' => ( isa => 'Int', is => 'rw' );
86
87 Internally, the metaclass for C<Point> has two
88 L<Moose::Meta::Attribute>. There are several methods for getting
89 meta-attributes out of a metaclass, one of which is
90 C<get_attribute_map>. This method is called on the metaclass object.
91
92 The C<get_attribute_map> method returns a hash reference that maps
93 attribute names to their objects. In our case, C<get_attribute_map>
94 might return something that looks like the following:
95
96   {
97       x => $attr_object_for_x,
98       y => $attr_object_for_y,
99   }
100
101 You can also get a single L<Moose::Meta::Attribute> with
102 C<get_attribute('name')>. Once you have this meta-attribute object,
103 you can call methods on it like this:
104
105   print $point->meta->get_attribute('x')->type_constraint;
106      => Int
107
108 To add a label to our attributes there are two steps. First, we need a
109 new attribute metaclass that can store a label for an
110 attribute. Second, we need to create attributes that use that
111 attribute metaclass.
112
113 =head1 RECIPE REVIEW
114
115 We start by  creating a new attribute metaclass.
116
117   package MyApp::Meta::Attribute::Labeled;
118   use Moose;
119   extends 'Moose::Meta::Attribute';
120
121 We can subclass a Moose metaclass in the same way that we subclass
122 anything else.
123
124   has label => (
125       is        => 'rw',
126       isa       => 'Str',
127       predicate => 'has_label',
128   );
129
130 Again, this is standard Moose code.
131
132 Then we need to register our metaclass with Moose:
133
134   package Moose::Meta::Attribute::Custom::Labeled;
135   sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
136
137 This is a bit of magic that lets us use a short name, "Labeled", when
138 referring to our new metaclass.
139
140 That was the whole attribute metaclass.
141
142 Now we start using it.
143
144   package MyApp::Website;
145   use Moose;
146   use MyApp::Meta::Attribute::Labeled;
147
148 We have to load the metaclass to use it, just like any Perl class.
149
150 Finally, we use it for an attribute:
151
152   has url => (
153       metaclass => 'Labeled',
154       is        => 'rw',
155       isa       => 'Str',
156       label     => "The site's URL",
157   );
158
159 This looks like a normal attribute declaration, except for two things,
160 the C<metaclass> and C<label> parameters. The C<metaclass> parameter
161 tells Moose we want to use a custom metaclass for this (one)
162 attribute. The C<label> parameter will be stored in the meta-attribute
163 object.
164
165 The reason that we can pass the name C<Labeled>, instead of
166 C<MyApp::Meta::Attribute::Labeled>, is because of the
167 C<register_implementation> code we touched on previously.
168
169 When you pass a metaclass to C<has>, it will take the name you provide
170 and prefix it with C<Moose::Meta::Attribute::Custom::>. Then it calls
171 C<register_implementation> in the package. In this case, that means
172 Moose ends up calling
173 C<Moose::Meta::Attribute::Custom::Labeled::register_implementation>.
174
175 If this function exists, it should return the I<real> metaclass
176 package name. This is exactly what our code does, returning
177 C<MyApp::Meta::Attribute::Labeled>. This is a little convoluted, and
178 if you don't like it, you can always use the fully-qualified name.
179
180 We can access this meta-attribute and its label like this:
181
182   $website->meta->get_attribute('url')->label()
183
184   MyApp::Website->meta->get_attribute('url')->label()
185
186 We also have a regular attribute, C<name>:
187
188   has name => (
189       is  => 'rw',
190       isa => 'Str',
191   );
192
193 This is a regular Moose attribute, because we have not specified a new
194 metaclass.
195
196 Finally, we have a C<dump> method, which creates a human-readable
197 representation of a C<MyApp::Website> object. It will use an
198 attribute's label if it has one.
199
200   sub dump {
201       my $self = shift;
202
203       my $dump = '';
204
205       my %attributes = %{ $self->meta->get_attribute_map };
206       for my $name ( sort keys %attributes ) {
207           my $attribute = $attributes{$name};
208
209           if (   $attribute->isa('MyApp::Meta::Attribute::Labeled')
210               && $attribute->has_label ) {
211               $dump .= $attribute->label;
212           }
213
214 This is a bit of defensive code. We cannot depend on every
215 meta-attribute having a label. Even if we define one for every
216 attribute in our class, a subclass may neglect to do so. Or a
217 superclass could add an attribute without a label.
218
219 We also check that the attribute has a label using the predicate we
220 defined. We could instead make the label C<required>. If we have a
221 label, we use it, otherwise we use the attribute name:
222
223           else {
224               $dump .= $name;
225           }
226
227           my $reader = $attribute->get_read_method;
228           $dump .= ": " . $self->$reader . "\n";
229       }
230
231       return $dump;
232   }
233
234 The C<get_read_method> is part of the L<Moose::Meta::Attribute>
235 API. It returns the name of a method that can read the attribute's
236 value, I<when called on the real object> (don't call this on the
237 meta-attribute).
238
239 =head1 CONCLUSION
240
241 You might wonder why you'd bother with all this. You could just
242 hardcode "The Site's URL" in the C<dump> method. But we want to avoid
243 repetition. If you need the label once, you may need it elsewhere,
244 maybe in the C<as_form> method you write next.
245
246 Associating a label with an attribute just makes sense! The label is a
247 piece of information I<about> the attribute.
248
249 It's also important to realize that this was a trivial example. You
250 can make much more powerful metaclasses that I<do> things, as opposed
251 to just storing some more information. For example, you could
252 implement a metaclass that expires attributes after a certain amount
253 of time:
254
255    has site_cache => (
256        metaclass     => 'TimedExpiry',
257        expires_after => { hours => 1 },
258        refresh_with  => sub { get( $_[0]->url ) },
259        isa           => 'Str',
260        is            => 'ro',
261    );
262
263 The sky's the limit!
264
265 =head1 AUTHOR
266
267 Shawn M Moore E<lt>sartak@gmail.comE<gt>
268
269 Dave Rolsky E<lt>autarch@urth.org<gt>
270
271 =head1 COPYRIGHT AND LICENSE
272
273 Copyright 2006-2009 by Infinity Interactive, Inc.
274
275 L<http://www.iinteractive.com>
276
277 This library is free software; you can redistribute it and/or modify
278 it under the same terms as Perl itself.
279
280 =begin testing
281
282 my $app = MyApp::Website->new( url => "http://google.com", name => "Google" );
283 is(
284     $app->dump, q{name: Google
285 The site's URL: http://google.com
286 }, '... got the expected dump value'
287 );
288
289 =end testing
290
291 =cut
292