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!