Revised recipe 2
Dave Rolsky [Thu, 12 Feb 2009 15:33:17 +0000 (15:33 +0000)]
lib/Moose/Cookbook/Meta/Recipe2.pod

index 4f82d42..013ed4c 100644 (file)
@@ -66,81 +66,61 @@ Moose::Cookbook::Meta::Recipe2 - A meta-attribute, attributes with labels
 
 =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.
+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.
 
-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 C<url> attribute with "The
-site's URL" and create a simple method to demonstrate how to use that label.
+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 REAL ATTRIBUTES 101
+=head1 META-ATTRIBUTE OBJECTS
 
-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.
+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' );
 
-Ah, 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<Moose::Meta::Attribute>.  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.
+Internally, the metaclass for C<Point> has two
+L<Moose::Meta::Attribute>. There are several methods for getting
+meta-attributes out of a metaclass, one of which is
+C<get_attribute_map>. This method is called on the metaclass object.
 
-So you have a C<$point> object, which has C<x> and C<y> methods. How can you
-actually access the objects behind these attributes? Here's one way:
-
-  $point->meta->get_attribute_map()
-
-C<get_attribute_map> returns a hash reference that maps attribute names to
-their objects. In our case, C<get_attribute_map> might return something that
-looks like the following:
+The C<get_attribute_map> method returns a hash reference that maps
+attribute names to their objects. In our case, C<get_attribute_map>
+might return something that looks like the following:
 
   {
-      x => Moose::Meta::Attribute=HASH(0x196c23c),
-      y => Moose::Meta::Attribute=HASH(0x18d1690),
+      x => $attr_object_for_x,
+      y => $attr_object_for_y,
   }
 
-Another way to get a handle on an attribute's object is
-C<< $self->meta->get_attribute('name') >>. Here's one thing you can do now that
-you can interact with the attribute's object directly:
+You can also get a single L<Moose::Meta::Attribute> with
+C<get_attribute('name')>. Once you have this meta-attribute object,
+you can call methods on it like this:
 
   print $point->meta->get_attribute('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
+To add a label to our attributes there are two steps. First, we need a
+new attribute metaclass that can store a label for an
+attribute. Second, we nede to create attributes that use that
+attribute metaclass.
 
-Moose makes both of these easy!
+=head1 RECIPE REVIEW
 
-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.
+We start by  creating a new attribute metaclass.
 
   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?)
+We can subclass a Moose metaclass in the same way that we subclass
+anything else.
 
   has label => (
       is        => 'rw',
@@ -148,30 +128,27 @@ credit: how in the actual hell can you use the MOP to extend itself?)
       predicate => 'has_label',
   );
 
-Hey, this looks pretty reasonable! This is plain-Jane Moose code. Recipe 1
-fare. This is merely making a new attribute. An attribute that attributes have.
-A meta-attribute. It may sound scary, but it really isn't! Reread
-L<REAL ATTRIBUTES 101> if this really is terrifying.
+Again, this is standard Moose code.
 
-The name is "label", it will have a regular accessor, and is a string.
-C<predicate> is a standard part of C<has>. It just creates a method that asks
-the question "Does this attribute have a value?"
+Then we need to register our metaclass with Moose:
 
   package Moose::Meta::Attribute::Custom::Labeled;
   sub register_implementation { 'MyApp::Meta::Attribute::Labeled' }
 
-This lets Moose discover our new metaclass. That way attributes can actually
-use it. More on what this is doing in a moment.
+This is a bit of magic that lets us use a short name, "Labeled", when
+referring to our new metaclas.
+
+That was the whole attribute metaclass.
 
-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.
+Now we start using it.
 
   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.
+We have to load the metaclass to use it, just like any Perl class.
+
+Finally, we use it for an attribute:
 
   has url => (
       metaclass => 'Labeled',
@@ -180,39 +157,46 @@ it.
       label     => "The site's URL",
   );
 
-Ah ha! Now we're using the metaclass. We're adding a new attribute, C<url>, to
-C<MyApp::Website>. C<has> lets you set the metaclass of the attribute.
-Ordinarily (as we've seen), the metaclass is C<Moose::Meta::Attribute>.
-
-When C<has> sees that you're using a new metaclass, it will take the
-metaclass's name, prepend C<Moose::Meta::Attribute::Custom::>, and call the
-C<register_implementation> function in that package. So here Moose calls
-C<Moose::Meta::Attribute::Custom::Labeled::register_implementation>. We defined
-that function in the beginning -- it just returns our "real" metaclass'
-package, C<MyApp::Meta::Attribute::Labeled>. 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. :)
-We also could have just defined the metaclass in
-C<Moose::Meta::Attribute::Custom::Labeled>, but it's probably better to keep to
-your own namespaces.
-
-Finally, we see that C<has> is setting our new meta-attribute, C<label>, to
-C<"The site's URL">. We can access this meta-attribute with:
+This looks like a normal attribute declaraion, except for two things,
+the C<metaclass> and C<label> parameters. The C<metaclass> parameter
+tells Moose we want to use a custom metaclass for this (one)
+attribute. The C<label> parameter will be stored in the meta-attribute
+object.
+
+The reason that we can pass the name C<Labeled>, instead of
+C<MyApp::Meta::Attribute::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::>. Then it calls
+C<register_implementation> in the package. In this case, that means
+Moose ends up calling
+C<Moose::Meta::Attribute::Custom::Labeled::register_implementation>.
+
+If this function exists, it should return the I<real> metaclass
+package name. This is exactly what our code does, returning
+C<MyApp::Meta::Attribute::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()
 
-Well, back to the code.
+  MyApp::Website->meta->get_attribute('url')->label()
+
+We also have a regular attribute, C<name>:
 
   has name => (
       is  => 'rw',
       isa => 'Str',
   );
 
-Of course, you don't have to use the new metaclass for B<all> new attributes.
+This is a regular Moose attribute, because we have not specified a new
+metaclass.
 
-Now we begin defining a method that will dump the C<MyApp::Website> instance
-for human readers.
+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;
@@ -221,72 +205,57 @@ for human readers.
       my %attributes = %{ $self->meta->get_attribute_map };
       while ( my ( $name, $attribute ) = each %attributes ) {
 
-Recall that C<get_attribute_map> returns a hashref of attribute names and their
-associated objects.
-
           # print the label if available
           if (   $attribute->isa('MyApp::Meta::Attribute::Labeled')
               && $attribute->has_label ) {
               print $attribute->label;
           }
 
-We have two checks here. The first is "is this attribute an instance of
-C<MyApp::Meta::Attribute::Labeled>?". It's good to code defensively. Even if
-all of your attributes have this metaclass, you never know when someone is
-going to subclass your work of art. Poorly. In other words, it's likely that
-there will still be (many) attributes that are instances of the default
-C<Moose::Meta::Attribute>.
+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.
 
-The second check is "does this attribute have a label?". This method was
-defined in the new metaclass as the "predicate". If we pass both checks, we
-print the attribute's 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 print it, otherwise we print the attribute name:
 
           # otherwise print the name
           else {
               print $name;
           }
 
-Another good, defensive coding practice: Provide reasonable defaults.
-
           # print the attribute's value
           my $reader = $attribute->get_read_method;
           print ": " . $self->$reader . "\n";
       }
   }
 
-Here's another example of using the attribute metaclass.
-C<< $attribute->get_read_method >> returns the name of the method that can
-be invoked on the original object to read the attribute's value.
-C<< $self->$reader >> is an example of "reflection" -- instead of using the
-name of the method, we're using a variable with the name of the method in it.
-Perl doesn't mind. Another way to write this would be
-C<< $self->can($reader)->($self) >>. Yuck. :)
-
-  package main;
-  my $app = MyApp::Website->new( url => "http://google.com", name => "Google" );
-  $app->dump;
-
-And we wrap up the example with a script to show off our newfound magic.
+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
 
-Why oh why would you want to go through all of these contortions when you can
-just print "The site's URL" directly in the C<dump> method? For one, the DRY
-(Don't Repeat Yourself) principle. If you have it in the C<dump> method, you'll
-probably also have it in the C<as_form> method, and C<to_file>, and so on. So
-why not have a method that maps attribute names to labels? That could work, but
-why not include the label where it belongs, in the attribute's definition?
-That way you're also less likely to forget to add the label.
+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.
 
-More importantly, this was a very simple example. Your metaclasses aren't
-limited to just adding new meta-attributes. For example, you could implement
-a metaclass that expires attributes after a certain amount of time. You
-might use it as such:
+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 => (
        metaclass     => 'TimedExpiry',
        expires_after => { hours => 1 },
-       refresh_with  => sub { get( $_->url ) },
+       refresh_with  => sub { get( $_[0]->url ) },
        isa           => 'Str',
        is            => 'ro',
    );
@@ -297,6 +266,8 @@ The sky's the limit!
 
 Shawn M Moore E<lt>sartak@gmail.comE<gt>
 
+Dave Rolsky E<lt>autarch@urth.org<gt>
+
 =head1 COPYRIGHT AND LICENSE
 
 Copyright 2006-2009 by Infinity Interactive, Inc.