X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FMoose%2FCookbook%2FMeta%2FRecipe3.pod;h=673d7fb9a2bf9ab65cb5352c534d42a3c244278c;hb=9327b01c56a220b4c09f73381bca577f49ad87a9;hp=329a410cd116c39a3090184db057b9ce3553e833;hpb=43aa5bf9dc7789a424aa69d6bac3ff456a979480;p=gitmo%2FMoose.git diff --git a/lib/Moose/Cookbook/Meta/Recipe3.pod b/lib/Moose/Cookbook/Meta/Recipe3.pod index 329a410..673d7fb 100644 --- a/lib/Moose/Cookbook/Meta/Recipe3.pod +++ b/lib/Moose/Cookbook/Meta/Recipe3.pod @@ -7,195 +7,193 @@ Moose::Cookbook::Meta::Recipe3 - Labels implemented via attribute traits =head1 SYNOPSIS - package MyApp::Meta::Attribute::Trait::Labeled; - use Moose::Role; - - has label => ( - is => 'rw', - isa => 'Str', - predicate => 'has_label', - ); - - package Moose::Meta::Attribute::Custom::Trait::Labeled; - sub register_implementation { 'MyApp::Meta::Attribute::Trait::Labeled' } - - package MyApp::Website; - use Moose; - use MyApp::Meta::Attribute::Trait::Labeled; - - 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; - - # iterate over all the attributes in $self - my %attributes = %{ $self->meta->get_attribute_map }; - while (my ($name, $attribute) = each %attributes) { - - # print the label if available - if ($attribute->does('MyApp::Meta::Attribute::Trait::Labeled') - && $attribute->has_label) { - print $attribute->label; - } - # otherwise print the name - else { - print $name; - } - - # print the attribute's value - my $reader = $attribute->get_read_method; - print ": " . $self->$reader . "\n"; - } - } - - package main; - my $app = MyApp::Website->new(url => "http://google.com", name => "Google"); - $app->dump; + package MyApp::Meta::Attribute::Trait::Labeled; + use Moose::Role; + + has label => ( + is => 'rw', + isa => 'Str', + predicate => 'has_label', + ); + + package Moose::Meta::Attribute::Custom::Trait::Labeled; + sub register_implementation {'MyApp::Meta::Attribute::Trait::Labeled'} + + package MyApp::Website; + use Moose; + use MyApp::Meta::Attribute::Trait::Labeled; + + 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; + + # iterate over all the attributes in $self + my %attributes = %{ $self->meta->get_attribute_map }; + while ( my ( $name, $attribute ) = each %attributes ) { + + # print the label if available + if ( $attribute->does('MyApp::Meta::Attribute::Trait::Labeled') + && $attribute->has_label ) { + print $attribute->label; + } + + # otherwise print the name + else { + print $name; + } + + # print the attribute's value + my $reader = $attribute->get_read_method; + print ": " . $self->$reader . "\n"; + } + } + + package main; + my $app = MyApp::Website->new( url => "http://google.com", name => "Google" ); + $app->dump; =head1 BUT FIRST -This recipe is a continuation of L. Please read that -first. +This recipe is a variation on +L. Please read that recipe first. =head1 MOTIVATION -In Recipe 21, we created an attribute metaclass that gives attributes a "label" -that can be set in L. That works well until you want a second -meta-attribute, or until you want to adjust the behavior of the attribute. You -could define a specialized attribute metaclass to use in every attribute. -However, you may want different attributes to have different behaviors. You -might end up with a unique attribute metaclass for B, -with a lot of code copying and pasting! +In L, we created an attribute +metaclass which lets you provide a label for attributes. -Or, if you've been drinking deeply of the Moose kool-aid, you'll have a role -for each of the behaviors. One role would give a label meta-attribute. Another -role would signify that this attribute is not directly modifiable via the -REST interface. Another role would write to a logfile when this attribute -was read. +Using a metaclass works fine until you realize you want to add a label +I an expiration, or some other combination of new behaviors. You +could create yet another metaclass which subclasses those two, but +that makes a mess, especially if you want to mix and match behaviors +across many attributes. -Unfortunately, you'd still be left with a bunch of attribute metaclasses that -do nothing but compose a bunch of roles. If only there were some way to specify -in L a list of roles to apply to the attribute metaclass... +Fortunately, Moose provides a much saner alternative, which is to +encapsulate each extension as a role, not a class. We can make a role +which adds a label to an attribute, and could make another to +implement expiration. =head1 TRAITS -Roles that apply to metaclasses have a special name: traits. Don't let the -change in nomenclature fool you, B. +Roles that apply to metaclasses have a special name: traits. Don't let +the change in nomenclature fool you, B. -L provides a C option. It takes a list of trait names to -compose into an anonymous metaclass. That means you do still have a bunch of -attribute metaclasses that do nothing but compose a bunch of roles, but they're -managed automatically by Moose. You don't need to declare them in advance, or -worry whether changing one will affect some other attribute. +L allows you to pass a C 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. -What can traits 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. +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 -A side-by-side look of the code examples in this recipe and recipe 21 should -indicate that defining and using a trait is very similar to defining and using -a new attribute metaclass. +A side-by-side look of the code examples in this recipe and recipe 2 +show that defining and using a trait is very similar to a full-blown +metaclass. - package MyApp::Meta::Attribute::Trait::Labeled; - use Moose::Role; + package MyApp::Meta::Attribute::Trait::Labeled; + use Moose::Role; - has label => ( - is => 'rw', - isa => 'Str', - predicate => 'has_label', - ); + has label => ( + is => 'rw', + isa => 'Str', + predicate => 'has_label', + ); -Instead of subclassing L, we define a role. Traits -don't need any special methods or attributes. You just focus on whatever it is -you actually need to get done. Here we're adding a new meta-attribute for use -in our application. +Instead of subclassing L, we define a role. As +with our metaclass in L, +registering our role allows us to refer to it by a short name. - package Moose::Meta::Attribute::Custom::Trait::Labeled; - sub register_implementation { 'MyApp::Meta::Attribute::Trait::Labeled' } + package Moose::Meta::Attribute::Custom::Trait::Labeled; + sub register_implementation { 'MyApp::Meta::Attribute::Trait::Labeled' } -Much like when we define a new attribute metaclass, we can provide a shorthand -name for the trait. Moose looks at the C method in +Moose looks for the C method in C to find the full name of the trait. -Now we begin writing our application logic. I'll only cover what has changed -since recipe 21. - - has url => ( - traits => [qw/Labeled/], - is => 'rw', - isa => 'Str', - label => "The site's URL", - ); - -L provides a C option. Just pass the list of trait names and -it will compose them together to form the (anonymous) attribute metaclass used -by the attribute. We provide a label for the attribute in the same way. - - # print the label if available - if ($attribute->does('MyApp::Meta::Attribute::Trait::Labeled') - && $attribute->has_label) { - print $attribute->label; - } - -Previously, this code asked the question "Does this attribute use our attribute -metaclass?" Since we're now using a trait, we ask "Does this attribute's -metaclass do the C role?" If not, the attribute metaclass won't have -the C method, and so it would be an error to blindly call -C<< $attribute->has_label >>. +For the rest of the code, we will only cover what is I from +L. + + has url => ( + traits => [qw/Labeled/], + is => 'rw', + isa => 'Str', + label => "The site's URL", + ); + +Instead of passing a C parameter, this time we pass +C. This contains a list of trait names. Moose will build an +anonymous attribute metaclass from these traits and use it for this +attribute. Passing a C