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