Update to modernize recommendations and make example code use modern APIs
[gitmo/Moose.git] / lib / Moose / Cookbook / Meta / Recipe2.pod
CommitLineData
daa0fd7d 1package Moose::Cookbook::Meta::Recipe2;
1edfdf1c 2
daa0fd7d 3# ABSTRACT: A meta-attribute, attributes with labels
4
5__END__
1edfdf1c 6
8323a774 7
daa0fd7d 8=pod
8323a774 9
8d8832a4 10=head1 SYNOPSIS
11
6a7e3999 12 package MyApp::Meta::Attribute::Labeled;
13 use Moose;
14 extends 'Moose::Meta::Attribute';
15
16 has label => (
17 is => 'rw',
18 isa => 'Str',
19 predicate => 'has_label',
20 );
21
22 package Moose::Meta::Attribute::Custom::Labeled;
23 sub register_implementation {'MyApp::Meta::Attribute::Labeled'}
24
25 package MyApp::Website;
26 use Moose;
6a7e3999 27
28 has url => (
29 metaclass => 'Labeled',
30 is => 'rw',
31 isa => 'Str',
32 label => "The site's URL",
33 );
34
35 has name => (
36 is => 'rw',
37 isa => 'Str',
38 );
39
40 sub dump {
41 my $self = shift;
42
ce444596 43 my $meta = $self->meta;
44
c79239a2 45 my $dump = '';
46
ce444596 47 for my $attribute ( map { $meta->get_attribute($_) }
48 sort $meta->get_attribute_list ) {
6a7e3999 49
6a7e3999 50 if ( $attribute->isa('MyApp::Meta::Attribute::Labeled')
51 && $attribute->has_label ) {
c79239a2 52 $dump .= $attribute->label;
6a7e3999 53 }
6a7e3999 54 else {
ce444596 55 $dump .= $attribute->name;
6a7e3999 56 }
57
6a7e3999 58 my $reader = $attribute->get_read_method;
c79239a2 59 $dump .= ": " . $self->$reader . "\n";
6a7e3999 60 }
c79239a2 61
62 return $dump;
6a7e3999 63 }
64
65 package main;
c79239a2 66
6a7e3999 67 my $app = MyApp::Website->new( url => "http://google.com", name => "Google" );
8323a774 68
69=head1 SUMMARY
70
de4b3855 71In this recipe, we begin to delve into the wonder of meta-programming.
72Some readers may scoff and claim that this is the arena of only the
73most twisted Moose developers. Absolutely not! Any sufficiently
74twisted developer can benefit greatly from going more meta.
8323a774 75
de4b3855 76Our goal is to allow each attribute to have a human-readable "label"
77attached to it. Such labels would be used when showing data to an end
78user. In this recipe we label the C<url> attribute with "The site's
79URL" and create a simple method showing how to use that label.
8323a774 80
772f7957 81The proper, modern way to extend attributes (using a role instead of a
82subclass) is described in L<Moose::Cookbook::Meta::Recipe3>, but that recipe
83assumes you've read and at least tried to understand this one.
84
de4b3855 85=head1 META-ATTRIBUTE OBJECTS
8323a774 86
de4b3855 87All the attributes of a Moose-based object are actually objects
88themselves. These objects have methods and attributes. Let's look at
89a concrete example.
8323a774 90
6a7e3999 91 has 'x' => ( isa => 'Int', is => 'ro' );
92 has 'y' => ( isa => 'Int', is => 'rw' );
8323a774 93
de4b3855 94Internally, the metaclass for C<Point> has two
95L<Moose::Meta::Attribute>. There are several methods for getting
96meta-attributes out of a metaclass, one of which is
ce444596 97C<get_attribute_list>. This method is called on the metaclass object.
8323a774 98
ce444596 99The C<get_attribute_list> method returns a list of attribute names. You can
100then use C<get_attribute> to get the L<Moose::Meta::Attribute> object itself.
8323a774 101
2a67e654 102Once you have this meta-attribute object, you can call methods on it like this:
8323a774 103
6a7e3999 104 print $point->meta->get_attribute('x')->type_constraint;
105 => Int
8323a774 106
de4b3855 107To add a label to our attributes there are two steps. First, we need a
108new attribute metaclass that can store a label for an
19320607 109attribute. Second, we need to create attributes that use that
de4b3855 110attribute metaclass.
8323a774 111
de4b3855 112=head1 RECIPE REVIEW
8323a774 113
2a67e654 114We start by creating a new attribute metaclass.
8323a774 115
6a7e3999 116 package MyApp::Meta::Attribute::Labeled;
117 use Moose;
118 extends 'Moose::Meta::Attribute';
8323a774 119
de4b3855 120We can subclass a Moose metaclass in the same way that we subclass
121anything else.
8323a774 122
6a7e3999 123 has label => (
124 is => 'rw',
125 isa => 'Str',
126 predicate => 'has_label',
127 );
8323a774 128
de4b3855 129Again, this is standard Moose code.
8323a774 130
de4b3855 131Then we need to register our metaclass with Moose:
8323a774 132
6a7e3999 133 package Moose::Meta::Attribute::Custom::Labeled;
134 sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
8323a774 135
de4b3855 136This is a bit of magic that lets us use a short name, "Labeled", when
19320607 137referring to our new metaclass.
de4b3855 138
139That was the whole attribute metaclass.
8323a774 140
de4b3855 141Now we start using it.
8323a774 142
6a7e3999 143 package MyApp::Website;
144 use Moose;
145 use MyApp::Meta::Attribute::Labeled;
8323a774 146
de4b3855 147We have to load the metaclass to use it, just like any Perl class.
148
149Finally, we use it for an attribute:
8323a774 150
6a7e3999 151 has url => (
152 metaclass => 'Labeled',
153 is => 'rw',
154 isa => 'Str',
155 label => "The site's URL",
156 );
8323a774 157
19320607 158This looks like a normal attribute declaration, except for two things,
de4b3855 159the C<metaclass> and C<label> parameters. The C<metaclass> parameter
160tells Moose we want to use a custom metaclass for this (one)
161attribute. The C<label> parameter will be stored in the meta-attribute
162object.
163
164The reason that we can pass the name C<Labeled>, instead of
165C<MyApp::Meta::Attribute::Labeled>, is because of the
166C<register_implementation> code we touched on previously.
167
168When you pass a metaclass to C<has>, it will take the name you provide
169and prefix it with C<Moose::Meta::Attribute::Custom::>. Then it calls
170C<register_implementation> in the package. In this case, that means
171Moose ends up calling
172C<Moose::Meta::Attribute::Custom::Labeled::register_implementation>.
173
174If this function exists, it should return the I<real> metaclass
175package name. This is exactly what our code does, returning
176C<MyApp::Meta::Attribute::Labeled>. This is a little convoluted, and
177if you don't like it, you can always use the fully-qualified name.
178
179We can access this meta-attribute and its label like this:
94acbcd7 180
6a7e3999 181 $website->meta->get_attribute('url')->label()
94acbcd7 182
de4b3855 183 MyApp::Website->meta->get_attribute('url')->label()
184
185We also have a regular attribute, C<name>:
8323a774 186
6a7e3999 187 has name => (
188 is => 'rw',
189 isa => 'Str',
190 );
8323a774 191
de4b3855 192This is a regular Moose attribute, because we have not specified a new
193metaclass.
8323a774 194
de4b3855 195Finally, we have a C<dump> method, which creates a human-readable
196representation of a C<MyApp::Website> object. It will use an
197attribute's label if it has one.
8323a774 198
6a7e3999 199 sub dump {
200 my $self = shift;
8323a774 201
ce444596 202 my $meta = $self->meta;
203
c79239a2 204 my $dump = '';
205
ce444596 206 for my $attribute ( map { $meta->get_attribute($_) }
207 sort $meta->get_attribute_list ) {
8323a774 208
6a7e3999 209 if ( $attribute->isa('MyApp::Meta::Attribute::Labeled')
210 && $attribute->has_label ) {
c79239a2 211 $dump .= $attribute->label;
6a7e3999 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
c79239a2 221label, we use it, otherwise we use the attribute name:
8323a774 222
6a7e3999 223 else {
ce444596 224 $dump .= $attribute->name;
6a7e3999 225 }
8323a774 226
6a7e3999 227 my $reader = $attribute->get_read_method;
c79239a2 228 $dump .= ": " . $self->$reader . "\n";
6a7e3999 229 }
c79239a2 230
231 return $dump;
6a7e3999 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
c79239a2 265=begin testing
266
267my $app = MyApp::Website->new( url => "http://google.com", name => "Google" );
268is(
269 $app->dump, q{name: Google
270The site's URL: http://google.com
271}, '... got the expected dump value'
272);
273
274=end testing
275
8323a774 276=cut
277