1Moose::Cookbook::LegacyU:s:eMLroaobCseoeln:et:drC_ioAbotukttbreoidobkuP:te:erLMleegtDaaocccylu:am:seLsna(tb3ae)tlieodn_AttributeMetaclass(3)
2
3
4
6 Moose::Cookbook::Legacy::Labeled_AttributeMetaclass - A meta-attribute,
7 attributes with labels
8
10 version 2.2011
11
13 package MyApp::Meta::Attribute::Labeled;
14 use Moose;
15 extends 'Moose::Meta::Attribute';
16
17 has label => (
18 is => 'rw',
19 isa => 'Str',
20 predicate => 'has_label',
21 );
22
23 package Moose::Meta::Attribute::Custom::Labeled;
24 sub register_implementation {'MyApp::Meta::Attribute::Labeled'}
25
26 package MyApp::Website;
27 use Moose;
28
29 has url => (
30 metaclass => 'Labeled',
31 is => 'rw',
32 isa => 'Str',
33 label => "The site's URL",
34 );
35
36 has name => (
37 is => 'rw',
38 isa => 'Str',
39 );
40
41 sub dump {
42 my $self = shift;
43
44 my $meta = $self->meta;
45
46 my $dump = '';
47
48 for my $attribute ( map { $meta->get_attribute($_) }
49 sort $meta->get_attribute_list ) {
50
51 if ( $attribute->isa('MyApp::Meta::Attribute::Labeled')
52 && $attribute->has_label ) {
53 $dump .= $attribute->label;
54 }
55 else {
56 $dump .= $attribute->name;
57 }
58
59 my $reader = $attribute->get_read_method;
60 $dump .= ": " . $self->$reader . "\n";
61 }
62
63 return $dump;
64 }
65
66 package main;
67
68 my $app = MyApp::Website->new( url => "http://google.com", name => "Google" );
69
71 WARNING: Subclassing metaclasses (as opposed to providing metaclass
72 traits) is strongly discouraged. This recipe is provided solely for
73 reference when encountering older code that does this.
74
75 In this recipe, we begin to delve into the wonder of meta-programming.
76 Some readers may scoff and claim that this is the arena of only the
77 most twisted Moose developers. Absolutely not! Any sufficiently twisted
78 developer can benefit greatly from going more meta.
79
80 Our goal is to allow each attribute to have a human-readable "label"
81 attached to it. Such labels would be used when showing data to an end
82 user. In this recipe we label the "url" attribute with "The site's URL"
83 and create a simple method showing how to use that label.
84
85 The proper, modern way to extend attributes (using a role instead of a
86 subclass) is described in Moose::Cookbook::Meta::Recipe3, but that
87 recipe assumes you've read and at least tried to understand this one.
88
90 All the attributes of a Moose-based object are actually objects
91 themselves. These objects have methods and attributes. Let's look at a
92 concrete example.
93
94 has 'x' => ( isa => 'Int', is => 'ro' );
95 has 'y' => ( isa => 'Int', is => 'rw' );
96
97 Internally, the metaclass for "Point" has two Moose::Meta::Attribute.
98 There are several methods for getting meta-attributes out of a
99 metaclass, one of which is "get_attribute_list". This method is called
100 on the metaclass object.
101
102 The "get_attribute_list" method returns a list of attribute names. You
103 can then use "get_attribute" to get the Moose::Meta::Attribute object
104 itself.
105
106 Once you have this meta-attribute object, you can call methods on it
107 like this:
108
109 print $point->meta->get_attribute('x')->type_constraint;
110 => Int
111
112 To add a label to our attributes there are two steps. First, we need a
113 new attribute metaclass that can store a label for an attribute.
114 Second, we need to create attributes that use that attribute metaclass.
115
117 We start by creating a new attribute metaclass.
118
119 package MyApp::Meta::Attribute::Labeled;
120 use Moose;
121 extends 'Moose::Meta::Attribute';
122
123 We can subclass a Moose metaclass in the same way that we subclass
124 anything else.
125
126 has label => (
127 is => 'rw',
128 isa => 'Str',
129 predicate => 'has_label',
130 );
131
132 Again, this is standard Moose code.
133
134 Then we need to register our metaclass with Moose:
135
136 package Moose::Meta::Attribute::Custom::Labeled;
137 sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
138
139 This is a bit of magic that lets us use a short name, "Labeled", when
140 referring to our new metaclass.
141
142 That was the whole attribute metaclass.
143
144 Now we start using it.
145
146 package MyApp::Website;
147 use Moose;
148 use MyApp::Meta::Attribute::Labeled;
149
150 We have to load the metaclass to use it, just like any Perl class.
151
152 Finally, we use it for an attribute:
153
154 has url => (
155 metaclass => 'Labeled',
156 is => 'rw',
157 isa => 'Str',
158 label => "The site's URL",
159 );
160
161 This looks like a normal attribute declaration, except for two things,
162 the "metaclass" and "label" parameters. The "metaclass" parameter tells
163 Moose we want to use a custom metaclass for this (one) attribute. The
164 "label" parameter will be stored in the meta-attribute object.
165
166 The reason that we can pass the name "Labeled", instead of
167 "MyApp::Meta::Attribute::Labeled", is because of the
168 "register_implementation" code we touched on previously.
169
170 When you pass a metaclass to "has", it will take the name you provide
171 and prefix it with "Moose::Meta::Attribute::Custom::". Then it calls
172 "register_implementation" in the package. In this case, that means
173 Moose ends up calling
174 "Moose::Meta::Attribute::Custom::Labeled::register_implementation".
175
176 If this function exists, it should return the real metaclass package
177 name. This is exactly what our code does, returning
178 "MyApp::Meta::Attribute::Labeled". This is a little convoluted, and if
179 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, "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 "dump" method, which creates a human-readable
198 representation of a "MyApp::Website" object. It will use an attribute's
199 label if it has one.
200
201 sub dump {
202 my $self = shift;
203
204 my $meta = $self->meta;
205
206 my $dump = '';
207
208 for my $attribute ( map { $meta->get_attribute($_) }
209 sort $meta->get_attribute_list ) {
210
211 if ( $attribute->isa('MyApp::Meta::Attribute::Labeled')
212 && $attribute->has_label ) {
213 $dump .= $attribute->label;
214 }
215
216 This is a bit of defensive code. We cannot depend on every meta-
217 attribute having a label. Even if we define one for every attribute in
218 our class, a subclass may neglect to do so. Or a superclass could add
219 an attribute without a label.
220
221 We also check that the attribute has a label using the predicate we
222 defined. We could instead make the label "required". If we have a
223 label, we use it, otherwise we use the attribute name:
224
225 else {
226 $dump .= $attribute->name;
227 }
228
229 my $reader = $attribute->get_read_method;
230 $dump .= ": " . $self->$reader . "\n";
231 }
232
233 return $dump;
234 }
235
236 The "get_read_method" is part of the Moose::Meta::Attribute API. It
237 returns the name of a method that can read the attribute's value, when
238 called on the real object (don't call this on the meta-attribute).
239
241 You might wonder why you'd bother with all this. You could just
242 hardcode "The Site's URL" in the "dump" method. But we want to avoid
243 repetition. If you need the label once, you may need it elsewhere,
244 maybe in the "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 about the attribute.
248
249 It's also important to realize that this was a trivial example. You can
250 make much more powerful metaclasses that do things, as opposed to just
251 storing some more information. For example, you could implement a
252 metaclass that expires attributes after a certain amount of time:
253
254 has site_cache => (
255 metaclass => 'TimedExpiry',
256 expires_after => { hours => 1 },
257 refresh_with => sub { get( $_[0]->url ) },
258 isa => 'Str',
259 is => 'ro',
260 );
261
262 The sky's the limit!
263
265 · Stevan Little <stevan.little@iinteractive.com>
266
267 · Dave Rolsky <autarch@urth.org>
268
269 · Jesse Luehrs <doy@tozt.net>
270
271 · Shawn M Moore <code@sartak.org>
272
273 · יובל קוג'מן (Yuval Kogman) <nothingmuch@woobling.org>
274
275 · Karen Etheridge <ether@cpan.org>
276
277 · Florian Ragwitz <rafl@debian.org>
278
279 · Hans Dieter Pearcey <hdp@weftsoar.net>
280
281 · Chris Prather <chris@prather.org>
282
283 · Matt S Trout <mst@shadowcat.co.uk>
284
286 This software is copyright (c) 2006 by Infinity Interactive, Inc.
287
288 This is free software; you can redistribute it and/or modify it under
289 the same terms as the Perl 5 programming language system itself.
290
291
292
293perl v5.28.1 Moose::Coo2k0b1o8o-k0:5:-L1e6gacy::Labeled_AttributeMetaclass(3)