From: Shawn M Moore Date: Sat, 24 May 2008 19:10:35 +0000 (+0000) Subject: Most of recipe 22 (attribute traits). Could use more fleshing out in the conclusion. X-Git-Tag: 0_55~150 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=aff0421c03cbf1083db6d62c4d381bc7c287f91c;p=gitmo%2FMoose.git Most of recipe 22 (attribute traits). Could use more fleshing out in the conclusion. --- diff --git a/MANIFEST b/MANIFEST index 3a789ed..9054e9c 100644 --- a/MANIFEST +++ b/MANIFEST @@ -19,6 +19,7 @@ lib/Moose/Cookbook/Recipe10.pod lib/Moose/Cookbook/Recipe11.pod lib/Moose/Cookbook/Recipe2.pod lib/Moose/Cookbook/Recipe21.pod +lib/Moose/Cookbook/Recipe22.pod lib/Moose/Cookbook/Recipe3.pod lib/Moose/Cookbook/Recipe4.pod lib/Moose/Cookbook/Recipe5.pod diff --git a/lib/Moose/Cookbook.pod b/lib/Moose/Cookbook.pod index d170acd..7ea1cb2 100644 --- a/lib/Moose/Cookbook.pod +++ b/lib/Moose/Cookbook.pod @@ -106,9 +106,12 @@ metaclasses. Attribute metaclasses let you extend attribute declarations (with C) and behavior to provide additional attribute functionality. -=item L - The meta-attribute trait example (TODO) +=item L - The meta-attribute trait example -I +Extending Moose's attribute metaclass is a great way to add +functionality. However, attributes can only have one metaclass. +Applying roles to the attribute metaclass lets you provide +composable attribute functionality. =item L - The meta-instance example (TODO) diff --git a/lib/Moose/Cookbook/Recipe22.pod b/lib/Moose/Cookbook/Recipe22.pod new file mode 100644 index 0000000..3fec735 --- /dev/null +++ b/lib/Moose/Cookbook/Recipe22.pod @@ -0,0 +1,186 @@ + +=pod + +=head1 NAME + +Moose::Cookbook::Recipe22 - The attribute trait example + +=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/], + 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, $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. + +=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 have a specialized attribute metaclass for your application that does it +all. However, you may want different attributes to have different behaviors. So +you may end up with a unique attribute metaclass for B, +with a lot of code copying and pasting. + +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. + +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... + +=head1 TRAITS + +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. So 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. + +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. + +=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. Only a few lines have changed. + + package MyApp::Meta::Attribute::Trait::Labeled; + use Moose::Role; + + 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. + + 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 shortcut +name for the trait. Moose looks at 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 +between recipe 21 and this. + + has url => ( + traits => [qw/Labeled/], + isa => 'Str', + is => 'rw', + 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 this attribute. + + # print the label if available + if ($attribute->does('MyApp::Meta::Attribute::Trait::Labeled') + && $attribute->has_label) { + print $attribute->label; + } + +Previously, this asked the question "Does this attribute use our attribute +metaclass?" But since we're now using a trait, we ask "Does this attribute's +metaclass do the C role?" If not, it won't have the C +method, and so it would be an error to blindly call +C<< $attribute->has_label >>. + +That's all. Everything else is the same! + +=head1 CONCLUSION + +If you're extending your attributes, it's easier and more flexible to provide +composable bits of behavior, rather than subclassing L. +Using traits (which are just roles applied to a metaclass!) let you choose +exactly which behaviors each attribute will have. Moose makes it easy to create +attribute metaclasses on the fly by providing a list of trait names to +L. + +=head1 AUTHOR + +Shawn M Moore Esartak@gmail.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2006-2008 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + +