Revised recipe 2
[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   use MyApp::Meta::Attribute::Labeled;
26
27   has url => (
28       metaclass => 'Labeled',
29       is        => 'rw',
30       isa       => 'Str',
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, $attribute ) = each %attributes ) {
45
46           # print the label if available
47           if (   $attribute->isa('MyApp::Meta::Attribute::Labeled')
48               && $attribute->has_label ) {
49               print $attribute->label;
50           }
51
52           # otherwise print the name
53           else {
54               print $name;
55           }
56
57           # print the attribute's value
58           my $reader = $attribute->get_read_method;
59           print ": " . $self->$reader . "\n";
60       }
61   }
62
63   package main;
64   my $app = MyApp::Website->new( url => "http://google.com", name => "Google" );
65   $app->dump;
66
67 =head1 SUMMARY
68
69 In this recipe, we begin to delve into the wonder of meta-programming.
70 Some readers may scoff and claim that this is the arena of only the
71 most twisted Moose developers. Absolutely not! Any sufficiently
72 twisted developer can benefit greatly from going more meta.
73
74 Our goal is to allow each attribute to have a human-readable "label"
75 attached to it. Such labels would be used when showing data to an end
76 user. In this recipe we label the C<url> attribute with "The site's
77 URL" and create a simple method showing how to use that label.
78
79 =head1 META-ATTRIBUTE OBJECTS
80
81 All the attributes of a Moose-based object are actually objects
82 themselves.  These objects have methods and attributes. Let's look at
83 a concrete example.
84
85   has 'x' => ( isa => 'Int', is => 'ro' );
86   has 'y' => ( isa => 'Int', is => 'rw' );
87
88 Internally, the metaclass for C<Point> has two
89 L<Moose::Meta::Attribute>. There are several methods for getting
90 meta-attributes out of a metaclass, one of which is
91 C<get_attribute_map>. This method is called on the metaclass object.
92
93 The C<get_attribute_map> method returns a hash reference that maps
94 attribute names to their objects. In our case, C<get_attribute_map>
95 might return something that looks like the following:
96
97   {
98       x => $attr_object_for_x,
99       y => $attr_object_for_y,
100   }
101
102 You can also get a single L<Moose::Meta::Attribute> with
103 C<get_attribute('name')>. Once you have this meta-attribute object,
104 you can call methods on it like this:
105
106   print $point->meta->get_attribute('x')->type_constraint;
107      => Int
108
109 To add a label to our attributes there are two steps. First, we need a
110 new attribute metaclass that can store a label for an
111 attribute. Second, we nede to create attributes that use that
112 attribute metaclass.
113
114 =head1 RECIPE REVIEW
115
116 We start by  creating a new attribute metaclass.
117
118   package MyApp::Meta::Attribute::Labeled;
119   use Moose;
120   extends 'Moose::Meta::Attribute';
121
122 We can subclass a Moose metaclass in the same way that we subclass
123 anything else.
124
125   has label => (
126       is        => 'rw',
127       isa       => 'Str',
128       predicate => 'has_label',
129   );
130
131 Again, this is standard Moose code.
132
133 Then we need to register our metaclass with Moose:
134
135   package Moose::Meta::Attribute::Custom::Labeled;
136   sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
137
138 This is a bit of magic that lets us use a short name, "Labeled", when
139 referring to our new metaclas.
140
141 That was the whole attribute metaclass.
142
143 Now we start using it.
144
145   package MyApp::Website;
146   use Moose;
147   use MyApp::Meta::Attribute::Labeled;
148
149 We have to load the metaclass to use it, just like any Perl class.
150
151 Finally, we use it for an attribute:
152
153   has url => (
154       metaclass => 'Labeled',
155       is        => 'rw',
156       isa       => 'Str',
157       label     => "The site's URL",
158   );
159
160 This looks like a normal attribute declaraion, except for two things,
161 the C<metaclass> and C<label> parameters. The C<metaclass> parameter
162 tells Moose we want to use a custom metaclass for this (one)
163 attribute. The C<label> parameter will be stored in the meta-attribute
164 object.
165
166 The reason that we can pass the name C<Labeled>, instead of
167 C<MyApp::Meta::Attribute::Labeled>, is because of the
168 C<register_implementation> code we touched on previously.
169
170 When you pass a metaclass to C<has>, it will take the name you provide
171 and prefix it with C<Moose::Meta::Attribute::Custom::>. Then it calls
172 C<register_implementation> in the package. In this case, that means
173 Moose ends up calling
174 C<Moose::Meta::Attribute::Custom::Labeled::register_implementation>.
175
176 If this function exists, it should return the I<real> metaclass
177 package name. This is exactly what our code does, returning
178 C<MyApp::Meta::Attribute::Labeled>. This is a little convoluted, and
179 if you don't like it, you can always use the fully-qualified name.
180
181 We can access this meta-attribute and its label like this:
182
183   $website->meta->get_attribute('url')->label()
184
185   MyApp::Website->meta->get_attribute('url')->label()
186
187 We also have a regular attribute, C<name>:
188
189   has name => (
190       is  => 'rw',
191       isa => 'Str',
192   );
193
194 This is a regular Moose attribute, because we have not specified a new
195 metaclass.
196
197 Finally, we have a C<dump> method, which creates a human-readable
198 representation of a C<MyApp::Website> object. It will use an
199 attribute's label if it has one.
200
201   sub dump {
202       my $self = shift;
203
204       # iterate over all the attributes in $self
205       my %attributes = %{ $self->meta->get_attribute_map };
206       while ( my ( $name, $attribute ) = each %attributes ) {
207
208           # print the label if available
209           if (   $attribute->isa('MyApp::Meta::Attribute::Labeled')
210               && $attribute->has_label ) {
211               print $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 print it, otherwise we print the attribute name:
222
223           # otherwise print the name
224           else {
225               print $name;
226           }
227
228           # print the attribute's value
229           my $reader = $attribute->get_read_method;
230           print ": " . $self->$reader . "\n";
231       }
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 =cut
281