ignore generated recipes
[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;
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;
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
91C<get_attribute_map>. This method is called on the metaclass object.
8323a774 92
de4b3855 93The C<get_attribute_map> method returns a hash reference that maps
94attribute names to their objects. In our case, C<get_attribute_map>
95might return something that looks like the following:
8323a774 96
6a7e3999 97 {
de4b3855 98 x => $attr_object_for_x,
99 y => $attr_object_for_y,
6a7e3999 100 }
8323a774 101
de4b3855 102You can also get a single L<Moose::Meta::Attribute> with
103C<get_attribute('name')>. Once you have this meta-attribute object,
104you can call methods on it like this:
8323a774 105
6a7e3999 106 print $point->meta->get_attribute('x')->type_constraint;
107 => Int
8323a774 108
de4b3855 109To add a label to our attributes there are two steps. First, we need a
110new attribute metaclass that can store a label for an
19320607 111attribute. Second, we need to create attributes that use that
de4b3855 112attribute metaclass.
8323a774 113
de4b3855 114=head1 RECIPE REVIEW
8323a774 115
de4b3855 116We start by creating a new attribute metaclass.
8323a774 117
6a7e3999 118 package MyApp::Meta::Attribute::Labeled;
119 use Moose;
120 extends 'Moose::Meta::Attribute';
8323a774 121
de4b3855 122We can subclass a Moose metaclass in the same way that we subclass
123anything else.
8323a774 124
6a7e3999 125 has label => (
126 is => 'rw',
127 isa => 'Str',
128 predicate => 'has_label',
129 );
8323a774 130
de4b3855 131Again, this is standard Moose code.
8323a774 132
de4b3855 133Then we need to register our metaclass with Moose:
8323a774 134
6a7e3999 135 package Moose::Meta::Attribute::Custom::Labeled;
136 sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
8323a774 137
de4b3855 138This is a bit of magic that lets us use a short name, "Labeled", when
19320607 139referring to our new metaclass.
de4b3855 140
141That was the whole attribute metaclass.
8323a774 142
de4b3855 143Now we start using it.
8323a774 144
6a7e3999 145 package MyApp::Website;
146 use Moose;
147 use MyApp::Meta::Attribute::Labeled;
8323a774 148
de4b3855 149We have to load the metaclass to use it, just like any Perl class.
150
151Finally, we use it for an attribute:
8323a774 152
6a7e3999 153 has url => (
154 metaclass => 'Labeled',
155 is => 'rw',
156 isa => 'Str',
157 label => "The site's URL",
158 );
8323a774 159
19320607 160This looks like a normal attribute declaration, except for two things,
de4b3855 161the C<metaclass> and C<label> parameters. The C<metaclass> parameter
162tells Moose we want to use a custom metaclass for this (one)
163attribute. The C<label> parameter will be stored in the meta-attribute
164object.
165
166The reason that we can pass the name C<Labeled>, instead of
167C<MyApp::Meta::Attribute::Labeled>, is because of the
168C<register_implementation> code we touched on previously.
169
170When you pass a metaclass to C<has>, it will take the name you provide
171and prefix it with C<Moose::Meta::Attribute::Custom::>. Then it calls
172C<register_implementation> in the package. In this case, that means
173Moose ends up calling
174C<Moose::Meta::Attribute::Custom::Labeled::register_implementation>.
175
176If this function exists, it should return the I<real> metaclass
177package name. This is exactly what our code does, returning
178C<MyApp::Meta::Attribute::Labeled>. This is a little convoluted, and
179if you don't like it, you can always use the fully-qualified name.
180
181We can access this meta-attribute and its label like this:
94acbcd7 182
6a7e3999 183 $website->meta->get_attribute('url')->label()
94acbcd7 184
de4b3855 185 MyApp::Website->meta->get_attribute('url')->label()
186
187We also have a regular attribute, C<name>:
8323a774 188
6a7e3999 189 has name => (
190 is => 'rw',
191 isa => 'Str',
192 );
8323a774 193
de4b3855 194This is a regular Moose attribute, because we have not specified a new
195metaclass.
8323a774 196
de4b3855 197Finally, we have a C<dump> method, which creates a human-readable
198representation of a C<MyApp::Website> object. It will use an
199attribute's label if it has one.
8323a774 200
6a7e3999 201 sub dump {
202 my $self = shift;
8323a774 203
6a7e3999 204 # iterate over all the attributes in $self
205 my %attributes = %{ $self->meta->get_attribute_map };
206 while ( my ( $name, $attribute ) = each %attributes ) {
8323a774 207
6a7e3999 208 # print the label if available
209 if ( $attribute->isa('MyApp::Meta::Attribute::Labeled')
210 && $attribute->has_label ) {
211 print $attribute->label;
212 }
8323a774 213
de4b3855 214This is a bit of defensive code. We cannot depend on every
215meta-attribute having a label. Even if we define one for every
216attribute in our class, a subclass may neglect to do so. Or a
217superclass could add an attribute without a label.
fe2d5aba 218
de4b3855 219We also check that the attribute has a label using the predicate we
220defined. We could instead make the label C<required>. If we have a
221label, we print it, otherwise we print the attribute name:
8323a774 222
6a7e3999 223 # otherwise print the name
224 else {
225 print $name;
226 }
8323a774 227
6a7e3999 228 # print the attribute's value
229 my $reader = $attribute->get_read_method;
230 print ": " . $self->$reader . "\n";
231 }
232 }
8323a774 233
de4b3855 234The C<get_read_method> is part of the L<Moose::Meta::Attribute>
235API. It returns the name of a method that can read the attribute's
236value, I<when called on the real object> (don't call this on the
237meta-attribute).
8323a774 238
239=head1 CONCLUSION
240
de4b3855 241You might wonder why you'd bother with all this. You could just
242hardcode "The Site's URL" in the C<dump> method. But we want to avoid
243repetition. If you need the label once, you may need it elsewhere,
244maybe in the C<as_form> method you write next.
245
246Associating a label with an attribute just makes sense! The label is a
247piece of information I<about> the attribute.
8323a774 248
de4b3855 249It's also important to realize that this was a trivial example. You
250can make much more powerful metaclasses that I<do> things, as opposed
251to just storing some more information. For example, you could
252implement a metaclass that expires attributes after a certain amount
253of time:
8323a774 254
6a7e3999 255 has site_cache => (
256 metaclass => 'TimedExpiry',
257 expires_after => { hours => 1 },
de4b3855 258 refresh_with => sub { get( $_[0]->url ) },
6a7e3999 259 isa => 'Str',
260 is => 'ro',
261 );
8323a774 262
263The sky's the limit!
264
265=head1 AUTHOR
266
267Shawn M Moore E<lt>sartak@gmail.comE<gt>
268
de4b3855 269Dave Rolsky E<lt>autarch@urth.org<gt>
270
8323a774 271=head1 COPYRIGHT AND LICENSE
272
2840a3b2 273Copyright 2006-2009 by Infinity Interactive, Inc.
8323a774 274
275L<http://www.iinteractive.com>
276
277This library is free software; you can redistribute it and/or modify
278it under the same terms as Perl itself.
279
280=cut
281