Moose Attribute Meta Meddling

| 2 Comments | No TrackBacks

This week I finally got a good excuse to go on and dabble with Moose meta-programming. In my WWW::Ohloh::API classes, a lot of attributes take their values from an initial xml snippet. The original, naive way those values were extracted was along the lines of:

has xml_src => (
    is   => 'ro',
    isa => 'XML::LibXML::Node',
);

has created_at => (
    is         => 'ro', 
    lazy      => 1,
    default => sub {
        return $_[0]->xml_src->findvalue( 'created_at' );
    }
)

has sender_account_name => (
    is          => 'ro', 
    lazy      => 1,
    default => sub {
        return $_[0]->xml_src->findvalue( 'sender_account_name' );
    }
);

Not harrowingly horrible, but rather repetitive. I figured out it would be much niftier to provide the name of the attribute holding the source xml and the pertinent xpath, and let Moose magic take care of the rest. Something like:

has created_at => (
    metaclass => 'XMLExtract',
    is => 'ro', 
    xml_src => 'xml_src',
    xpath => 'created_at',
)

Well, turns out that once one knows how, this is fairly easy to do:

package XMLExtract;

use Moose;

extends 'Moose::Meta::Attribute';

has 'xml_src' => ( isa => 'Str', );

has xpath => ( isa => 'Str', );

has '+lazy' => ( default => 1 );

before '_process_options' => sub {
    my ( $class, $name, $options ) = @_;

    die "attribute '$name' in class '$class' must be lazy-evaluated\n"
    if defined $options->{lazy} and not $options->{lazy};

    my $src = $options->{xml_src} ||= 'xml_src';
    my $xpath = $options->{xpath} ||= $name;

    $options->{default} = sub {
        return $_[0]->$src->findvalue($xpath);
    };

};

1;

If you notice, I've set 'xml_src' and 'xpath' to default to 'xml_src' and the name of the current attribute, so that the code can be further simplified to

has xml_src => (
    is   => 'ro',
    isa => 'XML::LibXML::Node',
);

has created_at => (
    metaclass => 'XMLExtract',
    is => 'ro', 
)

has sender_account_name => (
    metaclass => 'XMLExtract',
    is => 'ro', 
);

Short, sweet and to the point. Now we're talking code I like!

No TrackBacks

TrackBack URL: http://babyl.dyndns.org/mt/mt-tb.cgi/163

2 Comments

I think you should make this a "Trait" instead of a full metaclass. That way it can be composed with other attribute traits. (See MX::Getopt for an example.)

Yup, I realized that a few hours after posting the entry.
The morphing into a role is dead-easy too.
In XMLExtract I only have to change

use Moose;

extends 'Moose::Meta::Attribute';

for

use Moose::Role;

and its use for an attribute becomes

has created_at => (
    traits => ['WWW::Ohloh::API::Attr::XMLExtract'],
    is     => 'ro',
);


I must say, I'm beginning to warm up quite a lot to that Moose thing. :-)

Leave a comment

About this Entry

This page contains a single entry by Yanick published on May 2, 2009 12:47 AM.

Ruhr.pm: Die Meister des Psycho-Lamas was the previous entry in this blog.

Me, Moose and Summercamp 2009 is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.