From: Shawn M Moore Date: Sat, 20 Oct 2007 23:07:25 +0000 (+0000) Subject: Add a recipe for creating and using a new attribute metaclass X-Git-Tag: 0_27~23 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=8323a77419b0981dcba96b29cbd810e644610c74;p=gitmo%2FMoose.git Add a recipe for creating and using a new attribute metaclass This is probably too advanced for recipe 8, so it's recipe 11. :) --- diff --git a/lib/Moose/Cookbook/Recipe11.pod b/lib/Moose/Cookbook/Recipe11.pod new file mode 100644 index 0000000..09e4180 --- /dev/null +++ b/lib/Moose/Cookbook/Recipe11.pod @@ -0,0 +1,293 @@ + +=pod + +=head1 NAME + +Moose::Cookbook::Recipe11 - The meta-attribute example + + package MyApp::Meta::Attribute::Labeled; + use Moose; + extends 'Moose::Meta::Attribute'; + + __PACKAGE__->meta->add_attribute('label' => ( + reader => 'label', + predicate => 'has_label', + )); + + package Moose::Meta::Attribute::Custom::Labeled; + sub register_implementation { 'MyApp::Meta::Attribute::Labeled' } + + package MyApp::Website; + use Moose; + use MyApp::Meta::Attribute::Labeled; + + has url => ( + metaclass => 'Labeled', + isa => 'Str', + is => 'rw', + label => "The site's URL", + ); + + has name => ( + is => 'rw', + isa => 'Str', + ); + + sub dump { + my $self = shift; + + # iterate over all the attributes in $self + my %attributes = %{ $self->meta->get_attribute_map }; + while (my ($name, $meta_attribute) = each %attributes) { + + # print the label if available + if ($meta_attribute->isa('MyApp::Meta::Attribute::Labeled') + && $meta_attribute->has_label) { + print $meta_attribute->label; + } + # otherwise print the name + else { + print $name; + } + + # print the attribute's value + my $reader = $meta_attribute->get_read_method; + print ": " . $self->$reader . "\n"; + } + } + + package main; + my $app = MyApp::Website->new(url => "http://google.com", name => "Google"); + $app->dump; + +=head1 SUMMARY + +In this recipe, we begin to really delve into the wonder of meta-programming. +Some readers may scoff and claim that this is the arena only of the most +twisted Moose developers. Absolutely not! Any sufficiently twisted developer +can benefit greatly from going more meta. + +The high-level goal of this recipe's code 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 "url" attribute with "The +site's URL" and create a simple method to demonstrate how to use that label. + +=head1 REAL ATTRIBUTES 101 + +All the attributes of a Moose-based object are actually objects themselves. +These objects have methods and (surprisingly) attributes. Let's look at a +concrete example. + + has 'x' => (isa => 'Int', is => 'ro'); + has 'y' => (isa => 'Int', is => 'rw'); + +Ahh, the veritable x and y of the Point example. Internally, every Point has an +x object and a y object. They have methods (such as "get_value") and attributes +(such as "is_lazy"). What class are they instances of? +L. You don't normally see the objects lurking behind +the scenes, because you usually just use C<< $point->x >> and C<< $point->y >> +and forget that there's a lot of machinery lying in such methods. + +So you have a C<$point> object, which has C and C methods. How can you +actually access the objects behind these attributes? Here's one way: + + $point->meta->get_attribute_map() + +C returns a hash reference that maps attribute names to +their objects. In our case, C might return something that +looks like the following: + + { + x => Moose::Meta::Attribute=HASH(0x196c23c), + y => Moose::Meta::Attribute=HASH(0x18d1690), + } + +Here's one thing you can do now that you can interact with the attribute's +object directly: + + print $point->meta->get_attribute_map->{x}->type_constraint; + => Int + +(As an aside, it's not called C<< ->isa >> because C<< $obj->isa >> is already +taken) + +So to actually beef up attributes, what we need to do is: + +=over 4 + +=item Create a new attribute metaclass + +=item Create attributes using that new metaclass + +=back + +Moose makes both of these easy! + +Let's start dissecting the recipe's code. + +=head1 DISSECTION + +We get the ball rolling by creating a new attribute metaclass. It starts off +somewhat ungloriously. + + package MyApp::Meta::Attribute::Labeled; + use Moose; + extends 'Moose::Meta::Attribute'; + +You subclass metaclasses the same way you subclass regular classes. (Extra +credit: how in the actual hell can you use the MOP to extend itself?) Moving +on. + + __PACKAGE__->meta->add_attribute('label' => ( + reader => 'label', + predicate => 'has_label', + )); + +Now things get a little icky. We're adding a attribute to the attribute +metaclass. For clarity, I'm going to call this a meta-attribute. + +So. This creates a new meta-attribute in the C +metaclass. The new meta-attribute's name is 'label'. We get reader and +predicate methods, too. The reader method retrieves the value of this +meta-attribute, the predicate method just asks the question "Does this +meta-attribute even have a value?" + +Note the resemblance between C and C. C actually just +uses C behind the scenes. + + package Moose::Meta::Attribute::Custom::Labeled; + sub register_implementation { 'MyApp::Meta::Attribute::Labeled' } + +This registers the new metaclass with Moose. That way attributes can actually +use it. More on what this is doing in a moment. + +Note that we're done defining the new metaclass! Only nine lines of code, and +not particularly difficult lines, either. Now to start using the metaclass. + + package MyApp::Website; + use Moose; + use MyApp::Meta::Attribute::Labeled; + +Nothing new here. We do have to actually load our metaclass to be able to use +it. + + has url => ( + metaclass => 'Labeled', + isa => 'Str', + is => 'rw', + label => "The site's URL", + ); + +Ah ha! Now we're using the metaclass. We're adding a new attribute, C, to +C. C lets you set the metaclass of the attribute. Ordinarily (as we've seen), the metaclass is C. + +When C sees that you're using a new metaclass, it will take the +metaclass's name, prepend C, and call the +C function in that package. So here Moose calls +C. We +definited that function earlier, it just returns our "real" metaclass' package, +C. So Moose uses that metaclass for the +attribute. It may seem a bit convoluted, but the alternative would be to use +C<< metaclass => 'MyApp::Meta::Attribute::Labeled' >> on every attribute. As +usual, Moose optimizes in favor of the end user, not the metaprogrammer. :) + +Finally, we see that C is setting our new meta-attribute, C