This commit is contained in:
325
CPAN/Moose/Cookbook/Meta/Labeled_AttributeTrait.pod
Normal file
325
CPAN/Moose/Cookbook/Meta/Labeled_AttributeTrait.pod
Normal file
@@ -0,0 +1,325 @@
|
||||
# PODNAME: Moose::Cookbook::Meta::Labeled_AttributeTrait
|
||||
# ABSTRACT: Labels implemented via attribute traits
|
||||
|
||||
__END__
|
||||
|
||||
=pod
|
||||
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Moose::Cookbook::Meta::Labeled_AttributeTrait - Labels implemented via attribute traits
|
||||
|
||||
=head1 VERSION
|
||||
|
||||
version 2.2207
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
package MyApp::Meta::Attribute::Trait::Labeled;
|
||||
use Moose::Role;
|
||||
Moose::Util::meta_attribute_alias('Labeled');
|
||||
|
||||
has label => (
|
||||
is => 'rw',
|
||||
isa => 'Str',
|
||||
predicate => 'has_label',
|
||||
);
|
||||
|
||||
package MyApp::Website;
|
||||
use Moose;
|
||||
|
||||
has url => (
|
||||
traits => [qw/Labeled/],
|
||||
is => 'rw',
|
||||
isa => 'Str',
|
||||
label => "The site's URL",
|
||||
);
|
||||
|
||||
has name => (
|
||||
is => 'rw',
|
||||
isa => 'Str',
|
||||
);
|
||||
|
||||
sub dump {
|
||||
my $self = shift;
|
||||
|
||||
my $meta = $self->meta;
|
||||
|
||||
my $dump = '';
|
||||
|
||||
for my $attribute ( map { $meta->get_attribute($_) }
|
||||
sort $meta->get_attribute_list ) {
|
||||
|
||||
if ( $attribute->does('MyApp::Meta::Attribute::Trait::Labeled')
|
||||
&& $attribute->has_label ) {
|
||||
$dump .= $attribute->label;
|
||||
}
|
||||
else {
|
||||
$dump .= $attribute->name;
|
||||
}
|
||||
|
||||
my $reader = $attribute->get_read_method;
|
||||
$dump .= ": " . $self->$reader . "\n";
|
||||
}
|
||||
|
||||
return $dump;
|
||||
}
|
||||
|
||||
package main;
|
||||
|
||||
my $app = MyApp::Website->new( url => "http://google.com", name => "Google" );
|
||||
|
||||
=head1 SUMMARY
|
||||
|
||||
In this recipe, we begin to delve into the wonder of meta-programming.
|
||||
Some readers may scoff and claim that this is the arena of only the
|
||||
most twisted Moose developers. Absolutely not! Any sufficiently
|
||||
twisted developer can benefit greatly from going more meta.
|
||||
|
||||
Our goal is to allow each attribute to have a human-readable "label"
|
||||
attached to it. Such labels would be used when showing data to an end
|
||||
user. In this recipe we label the C<url> attribute with "The site's
|
||||
URL" and create a simple method showing how to use that label.
|
||||
|
||||
=head1 META-ATTRIBUTE OBJECTS
|
||||
|
||||
All the attributes of a Moose-based object are actually objects themselves.
|
||||
These objects have methods and attributes. Let's look at a concrete example.
|
||||
|
||||
has 'x' => ( isa => 'Int', is => 'ro' );
|
||||
has 'y' => ( isa => 'Int', is => 'rw' );
|
||||
|
||||
Internally, the metaclass for C<Point> has two L<Moose::Meta::Attribute>
|
||||
objects. There are several methods for getting meta-attributes out of a
|
||||
metaclass, one of which is C<get_attribute_list>. This method is called on the
|
||||
metaclass object.
|
||||
|
||||
The C<get_attribute_list> method returns a list of attribute names. You can
|
||||
then use C<get_attribute> to get the L<Moose::Meta::Attribute> object itself.
|
||||
|
||||
Once you have this meta-attribute object, you can call methods on it like
|
||||
this:
|
||||
|
||||
print $point->meta->get_attribute('x')->type_constraint;
|
||||
=> Int
|
||||
|
||||
To add a label to our attributes there are two steps. First, we need a new
|
||||
attribute metaclass trait that can store a label for an attribute. Second, we
|
||||
need to apply that trait to our attributes.
|
||||
|
||||
=head1 TRAITS
|
||||
|
||||
Roles that apply to metaclasses have a special name: traits. Don't let
|
||||
the change in nomenclature fool you, B<traits are just roles>.
|
||||
|
||||
L<Moose/has> allows you to pass a C<traits> parameter for an
|
||||
attribute. This parameter takes a list of trait names which are
|
||||
composed into an anonymous metaclass, and that anonymous metaclass is
|
||||
used for the attribute.
|
||||
|
||||
Yes, we still have lots of metaclasses in the background, but they're
|
||||
managed by Moose for you.
|
||||
|
||||
Traits can do anything roles can do. They can add or refine
|
||||
attributes, wrap methods, provide more methods, define an interface,
|
||||
etc. The only difference is that you're now changing the attribute
|
||||
metaclass instead of a user-level class.
|
||||
|
||||
=head1 DISSECTION
|
||||
|
||||
We start by creating a package for our trait.
|
||||
|
||||
package MyApp::Meta::Attribute::Trait::Labeled;
|
||||
use Moose::Role;
|
||||
|
||||
has label => (
|
||||
is => 'rw',
|
||||
isa => 'Str',
|
||||
predicate => 'has_label',
|
||||
);
|
||||
|
||||
You can see that a trait is just a L<Moose::Role>. In this case, our role
|
||||
contains a single attribute, C<label>. Any attribute which does this trait
|
||||
will now have a label.
|
||||
|
||||
We also register our trait with Moose:
|
||||
|
||||
Moose::Util::meta_attribute_alias('Labeled');
|
||||
|
||||
This allows Moose to find our trait by the short name C<Labeled> when passed
|
||||
to the C<traits> attribute option, rather than requiring the full package
|
||||
name to be specified.
|
||||
|
||||
Finally, we pass our trait when defining an attribute:
|
||||
|
||||
has url => (
|
||||
traits => [qw/Labeled/],
|
||||
is => 'rw',
|
||||
isa => 'Str',
|
||||
label => "The site's URL",
|
||||
);
|
||||
|
||||
The C<traits> parameter contains a list of trait names. Moose will build an
|
||||
anonymous attribute metaclass from these traits and use it for this
|
||||
attribute.
|
||||
|
||||
The reason that we can pass the name C<Labeled>, instead of
|
||||
C<MyApp::Meta::Attribute::Trait::Labeled>, is because of the
|
||||
C<register_implementation> code we touched on previously.
|
||||
|
||||
When you pass a metaclass to C<has>, it will take the name you provide and
|
||||
prefix it with C<Moose::Meta::Attribute::Custom::Trait::>. Then it calls
|
||||
C<register_implementation> in the package. In this case, that means Moose ends
|
||||
up calling
|
||||
C<Moose::Meta::Attribute::Custom::Trait::Labeled::register_implementation>.
|
||||
|
||||
If this function exists, it should return the I<real> trait's package
|
||||
name. This is exactly what our code does, returning
|
||||
C<MyApp::Meta::Attribute::Trait::Labeled>. This is a little convoluted, and if
|
||||
you don't like it, you can always use the fully-qualified name.
|
||||
|
||||
We can access this meta-attribute and its label like this:
|
||||
|
||||
$website->meta->get_attribute('url')->label()
|
||||
|
||||
MyApp::Website->meta->get_attribute('url')->label()
|
||||
|
||||
We also have a regular attribute, C<name>:
|
||||
|
||||
has name => (
|
||||
is => 'rw',
|
||||
isa => 'Str',
|
||||
);
|
||||
|
||||
Finally, we have a C<dump> method, which creates a human-readable
|
||||
representation of a C<MyApp::Website> object. It will use an attribute's label
|
||||
if it has one.
|
||||
|
||||
sub dump {
|
||||
my $self = shift;
|
||||
|
||||
my $meta = $self->meta;
|
||||
|
||||
my $dump = '';
|
||||
|
||||
for my $attribute ( map { $meta->get_attribute($_) }
|
||||
sort $meta->get_attribute_list ) {
|
||||
|
||||
if ( $attribute->does('MyApp::Meta::Attribute::Trait::Labeled')
|
||||
&& $attribute->has_label ) {
|
||||
$dump .= $attribute->label;
|
||||
}
|
||||
|
||||
This is a bit of defensive code. We cannot depend on every meta-attribute
|
||||
having a label. Even if we define one for every attribute in our class, a
|
||||
subclass may neglect to do so. Or a superclass could add an attribute without
|
||||
a label.
|
||||
|
||||
We also check that the attribute has a label using the predicate we
|
||||
defined. We could instead make the label C<required>. If we have a label, we
|
||||
use it, otherwise we use the attribute name:
|
||||
|
||||
else {
|
||||
$dump .= $attribute->name;
|
||||
}
|
||||
|
||||
my $reader = $attribute->get_read_method;
|
||||
$dump .= ": " . $self->$reader . "\n";
|
||||
}
|
||||
|
||||
return $dump;
|
||||
}
|
||||
|
||||
The C<get_read_method> is part of the L<Moose::Meta::Attribute> API. It
|
||||
returns the name of a method that can read the attribute's value, I<when
|
||||
called on the real object> (don't call this on the meta-attribute).
|
||||
|
||||
=head1 CONCLUSION
|
||||
|
||||
You might wonder why you'd bother with all this. You could just hardcode "The
|
||||
Site's URL" in the C<dump> method. But we want to avoid repetition. If you
|
||||
need the label once, you may need it elsewhere, maybe in the C<as_form> method
|
||||
you write next.
|
||||
|
||||
Associating a label with an attribute just makes sense! The label is a piece
|
||||
of information I<about> the attribute.
|
||||
|
||||
It's also important to realize that this was a trivial example. You can make
|
||||
much more powerful metaclasses that I<do> things, as opposed to just storing
|
||||
some more information. For example, you could implement a metaclass that
|
||||
expires attributes after a certain amount of time:
|
||||
|
||||
has site_cache => (
|
||||
traits => ['TimedExpiry'],
|
||||
expires_after => { hours => 1 },
|
||||
refresh_with => sub { get( $_[0]->url ) },
|
||||
isa => 'Str',
|
||||
is => 'ro',
|
||||
);
|
||||
|
||||
The sky's the limit!
|
||||
|
||||
=for testing my $app
|
||||
= MyApp::Website->new( url => 'http://google.com', name => 'Google' );
|
||||
is(
|
||||
$app->dump, q{name: Google
|
||||
The site's URL: http://google.com
|
||||
}, '... got the expected dump value'
|
||||
);
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
Stevan Little <stevan@cpan.org>
|
||||
|
||||
=item *
|
||||
|
||||
Dave Rolsky <autarch@urth.org>
|
||||
|
||||
=item *
|
||||
|
||||
Jesse Luehrs <doy@cpan.org>
|
||||
|
||||
=item *
|
||||
|
||||
Shawn M Moore <sartak@cpan.org>
|
||||
|
||||
=item *
|
||||
|
||||
יובל קוג'מן (Yuval Kogman) <nothingmuch@woobling.org>
|
||||
|
||||
=item *
|
||||
|
||||
Karen Etheridge <ether@cpan.org>
|
||||
|
||||
=item *
|
||||
|
||||
Florian Ragwitz <rafl@debian.org>
|
||||
|
||||
=item *
|
||||
|
||||
Hans Dieter Pearcey <hdp@cpan.org>
|
||||
|
||||
=item *
|
||||
|
||||
Chris Prather <chris@prather.org>
|
||||
|
||||
=item *
|
||||
|
||||
Matt S Trout <mstrout@cpan.org>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
This software is copyright (c) 2006 by Infinity Interactive, Inc.
|
||||
|
||||
This is free software; you can redistribute it and/or modify it under
|
||||
the same terms as the Perl 5 programming language system itself.
|
||||
|
||||
=cut
|
||||
Reference in New Issue
Block a user