Mention that we could use a trigger instead of a before modifier in
[gitmo/Moose.git] / lib / Moose / Cookbook / Meta / Recipe3.pod
index 1faeaca..673d7fb 100644 (file)
@@ -7,196 +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
+This recipe is a variation on
 L<Moose::Cookbook::Meta::Recipe2>. Please read that recipe first.
 
 =head1 MOTIVATION
 
 In L<Moose::Cookbook::Meta::Recipe2>, we created an attribute
-metaclass that gives attributes a "label" that can be set in
-L<Moose/has>. 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<every single attribute>, 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<Moose/has> a list of roles to apply to the attribute metaclass...
+metaclass which lets you provide a label for attributes.
+
+Using a metaclass works fine until you realize you want to add a label
+I<and> 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.
+
+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<traits are just roles>.
+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> provides a C<traits> 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<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.
 
-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 2 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<Moose::Meta::Attribute>, 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<Moose::Meta::Attribute>, we define a role. As
+with our metaclass in L<recipe 2|Moose::Cookbook::Meta::Recipe2>,
+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<register_implementation> method in
+Moose looks for the C<register_implementation> method in
 C<Moose::Meta::Attribute::Custom::Trait::$TRAIT_NAME> to find the full
 name of the trait.
 
-Now we begin writing our application logic. I'll only cover what has changed
-since recipe 2.
-
-    has url => (
-        traits => [qw/Labeled/],
-        is     => 'rw',
-        isa    => 'Str',
-        label  => "The site's URL",
-    );
-
-L<Moose/has> provides a C<traits> 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<Labeled> role?" If not, the attribute metaclass won't have
-the C<has_label> 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<different> from
+L<recipe 2|Moose::Cookbook::Meta::Recipe2>.
+
+  has url => (
+      traits => [qw/Labeled/],
+      is     => 'rw',
+      isa    => 'Str',
+      label  => "The site's URL",
+  );
+
+Instead of passing a C<metaclass> parameter, this time we pass
+C<traits>. 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<label> parameter works just as it did with the
+metaclass example.
+
+  # print the label if available
+  if (   $attribute->does('MyApp::Meta::Attribute::Trait::Labeled')
+      && $attribute->has_label ) {
+      print $attribute->label;
+  }
+
+In the metaclass example, we used C<< $attribute->isa >>. With a role,
+we instead ask if the meta-attribute object C<does> the required
+role. If it does not do this role, the attribute meta object won't
+have the C<has_label> method.
 
 That's all. Everything else is the same!
 
-=head1 METACLASS + TRAIT
+=head1 TURNING A METACLASS INTO A TRAIT
 
 "But wait!" you protest. "I've already written all of my extensions as
 attribute metaclasses. I don't want to break all that code out there."
 
-All is not lost. If you rewrite your extension as a trait, then you can
-easily get a regular metaclass extension out of it. You just compose the trait
-in the attribute metaclass, as normal.
+Fortunately, you can easily turn a metaclass into a trait and still
+provide the original metaclass:
 
-    package MyApp::Meta::Attribute::Labeled;
-    use Moose;
-    extends 'Moose::Meta::Attribute';
-    with 'MyApp::Meta::Attribute::Trait::Labeled';
+  package MyApp::Meta::Attribute::Labeled;
+  use Moose;
+  extends 'Moose::Meta::Attribute';
+  with 'MyApp::Meta::Attribute::Trait::Labeled';
 
-    package Moose::Meta::Attribute::Custom::Labeled;
-    sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
+  package Moose::Meta::Attribute::Custom::Labeled;
+  sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
 
-Unfortunately, going the other way (providing a trait created from a metaclass)
-is more tricky. Thus, defining your extensions as traits is just plain better
-than defining them as subclassed metaclasses.
+Unfortunately, going the other way (providing a trait created from a
+metaclass) is more tricky.
 
 =head1 CONCLUSION
 
-If you're extending your attributes, it's easier and more flexible to provide
-composable bits of behavior than to subclass L<Moose::Meta::Attribute>.
-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<Moose/has>.
+If you're extending your attributes, it's easier and more flexible to
+provide composable bits of behavior than to subclass
+L<Moose::Meta::Attribute>. Using traits lets you cooperate with other
+extensions, either from CPAN or that you might write in the
+future. Moose makes it easy to create attribute metaclasses on the fly
+by providing a list of trait names to L<Moose/has>.
 
 =head1 AUTHOR
 
 Shawn M Moore E<lt>sartak@gmail.comE<gt>
 
+Dave Rolsky E<lt>autarch@urth.org<gt>
+
 =head1 COPYRIGHT AND LICENSE
 
-Copyright 2006-2008 by Infinity Interactive, Inc.
+Copyright 2006-2009 by Infinity Interactive, Inc.
 
 L<http://www.iinteractive.com>