1Moose::Cookbook::Meta::ULsaebrelCeodn_tMArotiotbsrueit:be:udCtoePoTekrrbaloiotDk(o:3c:)uMmeetnat:a:tLiaobneled_AttributeTrait(3)
2
3
4
6 Moose::Cookbook::Meta::Labeled_AttributeTrait - Labels implemented via
7 attribute traits
8
10 version 2.2014
11
13 package MyApp::Meta::Attribute::Trait::Labeled;
14 use Moose::Role;
15 Moose::Util::meta_attribute_alias('Labeled');
16
17 has label => (
18 is => 'rw',
19 isa => 'Str',
20 predicate => 'has_label',
21 );
22
23 package MyApp::Website;
24 use Moose;
25
26 has url => (
27 traits => [qw/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
41 my $meta = $self->meta;
42
43 my $dump = '';
44
45 for my $attribute ( map { $meta->get_attribute($_) }
46 sort $meta->get_attribute_list ) {
47
48 if ( $attribute->does('MyApp::Meta::Attribute::Trait::Labeled')
49 && $attribute->has_label ) {
50 $dump .= $attribute->label;
51 }
52 else {
53 $dump .= $attribute->name;
54 }
55
56 my $reader = $attribute->get_read_method;
57 $dump .= ": " . $self->$reader . "\n";
58 }
59
60 return $dump;
61 }
62
63 package main;
64
65 my $app = MyApp::Website->new( url => "http://google.com", name => "Google" );
66
68 In this recipe, we begin to delve into the wonder of meta-programming.
69 Some readers may scoff and claim that this is the arena of only the
70 most twisted Moose developers. Absolutely not! Any sufficiently twisted
71 developer can benefit greatly from going more meta.
72
73 Our goal is to allow each attribute to have a human-readable "label"
74 attached to it. Such labels would be used when showing data to an end
75 user. In this recipe we label the "url" attribute with "The site's URL"
76 and create a simple method showing how to use that label.
77
79 All the attributes of a Moose-based object are actually objects
80 themselves. These objects have methods and attributes. Let's look at a
81 concrete example.
82
83 has 'x' => ( isa => 'Int', is => 'ro' );
84 has 'y' => ( isa => 'Int', is => 'rw' );
85
86 Internally, the metaclass for "Point" has two Moose::Meta::Attribute
87 objects. There are several methods for getting meta-attributes out of a
88 metaclass, one of which is "get_attribute_list". This method is called
89 on the metaclass object.
90
91 The "get_attribute_list" method returns a list of attribute names. You
92 can then use "get_attribute" to get the Moose::Meta::Attribute object
93 itself.
94
95 Once you have this meta-attribute object, you can call methods on it
96 like this:
97
98 print $point->meta->get_attribute('x')->type_constraint;
99 => Int
100
101 To add a label to our attributes there are two steps. First, we need a
102 new attribute metaclass trait that can store a label for an attribute.
103 Second, we need to apply that trait to our attributes.
104
106 Roles that apply to metaclasses have a special name: traits. Don't let
107 the change in nomenclature fool you, traits are just roles.
108
109 "has" in Moose allows you to pass a "traits" parameter for an
110 attribute. This parameter takes a list of trait names which are
111 composed into an anonymous metaclass, and that anonymous metaclass is
112 used for the attribute.
113
114 Yes, we still have lots of metaclasses in the background, but they're
115 managed by Moose for you.
116
117 Traits can do anything roles can do. They can add or refine attributes,
118 wrap methods, provide more methods, define an interface, etc. The only
119 difference is that you're now changing the attribute metaclass instead
120 of a user-level class.
121
123 We start by creating a package for our trait.
124
125 package MyApp::Meta::Attribute::Trait::Labeled;
126 use Moose::Role;
127
128 has label => (
129 is => 'rw',
130 isa => 'Str',
131 predicate => 'has_label',
132 );
133
134 You can see that a trait is just a Moose::Role. In this case, our role
135 contains a single attribute, "label". Any attribute which does this
136 trait will now have a label.
137
138 We also register our trait with Moose:
139
140 Moose::Util::meta_attribute_alias('Labeled');
141
142 This allows Moose to find our trait by the short name "Labeled" when
143 passed to the "traits" attribute option, rather than requiring the full
144 package name to be specified.
145
146 Finally, we pass our trait when defining an attribute:
147
148 has url => (
149 traits => [qw/Labeled/],
150 is => 'rw',
151 isa => 'Str',
152 label => "The site's URL",
153 );
154
155 The "traits" parameter contains a list of trait names. Moose will build
156 an anonymous attribute metaclass from these traits and use it for this
157 attribute.
158
159 The reason that we can pass the name "Labeled", instead of
160 "MyApp::Meta::Attribute::Trait::Labeled", is because of the
161 "register_implementation" code we touched on previously.
162
163 When you pass a metaclass to "has", it will take the name you provide
164 and prefix it with "Moose::Meta::Attribute::Custom::Trait::". Then it
165 calls "register_implementation" in the package. In this case, that
166 means Moose ends up calling
167 "Moose::Meta::Attribute::Custom::Trait::Labeled::register_implementation".
168
169 If this function exists, it should return the real trait's package
170 name. This is exactly what our code does, returning
171 "MyApp::Meta::Attribute::Trait::Labeled". This is a little convoluted,
172 and if you don't like it, you can always use the fully-qualified name.
173
174 We can access this meta-attribute and its label like this:
175
176 $website->meta->get_attribute('url')->label()
177
178 MyApp::Website->meta->get_attribute('url')->label()
179
180 We also have a regular attribute, "name":
181
182 has name => (
183 is => 'rw',
184 isa => 'Str',
185 );
186
187 Finally, we have a "dump" method, which creates a human-readable
188 representation of a "MyApp::Website" object. It will use an attribute's
189 label if it has one.
190
191 sub dump {
192 my $self = shift;
193
194 my $meta = $self->meta;
195
196 my $dump = '';
197
198 for my $attribute ( map { $meta->get_attribute($_) }
199 sort $meta->get_attribute_list ) {
200
201 if ( $attribute->does('MyApp::Meta::Attribute::Trait::Labeled')
202 && $attribute->has_label ) {
203 $dump .= $attribute->label;
204 }
205
206 This is a bit of defensive code. We cannot depend on every meta-
207 attribute having a label. Even if we define one for every attribute in
208 our class, a subclass may neglect to do so. Or a superclass could add
209 an attribute without a label.
210
211 We also check that the attribute has a label using the predicate we
212 defined. We could instead make the label "required". If we have a
213 label, we use it, otherwise we use the attribute name:
214
215 else {
216 $dump .= $attribute->name;
217 }
218
219 my $reader = $attribute->get_read_method;
220 $dump .= ": " . $self->$reader . "\n";
221 }
222
223 return $dump;
224 }
225
226 The "get_read_method" is part of the Moose::Meta::Attribute API. It
227 returns the name of a method that can read the attribute's value, when
228 called on the real object (don't call this on the meta-attribute).
229
231 You might wonder why you'd bother with all this. You could just
232 hardcode "The Site's URL" in the "dump" method. But we want to avoid
233 repetition. If you need the label once, you may need it elsewhere,
234 maybe in the "as_form" method you write next.
235
236 Associating a label with an attribute just makes sense! The label is a
237 piece of information about the attribute.
238
239 It's also important to realize that this was a trivial example. You can
240 make much more powerful metaclasses that do things, as opposed to just
241 storing some more information. For example, you could implement a
242 metaclass that expires attributes after a certain amount of time:
243
244 has site_cache => (
245 traits => ['TimedExpiry'],
246 expires_after => { hours => 1 },
247 refresh_with => sub { get( $_[0]->url ) },
248 isa => 'Str',
249 is => 'ro',
250 );
251
252 The sky's the limit!
253
255 • Stevan Little <stevan@cpan.org>
256
257 • Dave Rolsky <autarch@urth.org>
258
259 • Jesse Luehrs <doy@cpan.org>
260
261 • Shawn M Moore <sartak@cpan.org>
262
263 • יובל קוג'מן (Yuval Kogman) <nothingmuch@woobling.org>
264
265 • Karen Etheridge <ether@cpan.org>
266
267 • Florian Ragwitz <rafl@debian.org>
268
269 • Hans Dieter Pearcey <hdp@cpan.org>
270
271 • Chris Prather <chris@prather.org>
272
273 • Matt S Trout <mstrout@cpan.org>
274
276 This software is copyright (c) 2006 by Infinity Interactive, Inc.
277
278 This is free software; you can redistribute it and/or modify it under
279 the same terms as the Perl 5 programming language system itself.
280
281
282
283perl v5.32.1 Moos2e0:2:1C-o0o1k-b2o7ok::Meta::Labeled_AttributeTrait(3)