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