Using Moose Metadata

As many of you know, the Moose module has a metaobject protocol, but it's not something that many casual hackers use. Truth be told, I don't use it a lot either, but when I do, it saves me a lot of hassle. I've been writing an extremely complicated data importer and at the end, I didn't so much need a summary of the data, but a summary of what the importer did. That's when Moose metadata made my life so much easier and my code more maintainable.

In my code, I import a ton of data. I do the usual add, update and delete. Sometimes I count errors. I have a bunch of attributes defined as counters. They look like this (these are a subset):

foreach my $type (qw/sites persons/) {
    foreach my $status (qw/new updated deleted/) {
        has "num_${status}_$type" => (
            traits        => ['Counter'],
            is            => 'ro',
            isa           => 'Int',
            default       => 0,
            handles       => { "_inc_${status}_$type" => 'inc' },
            documentation => "Number of $status $type",
        );
    }
}

has "current_record_number" => (
    traits        => ['Counter'],
    is            => 'ro',
    isa           => 'Int',
    default       => 0,
    handles       => { "_inc_current_record" => 'inc' },
    documentation => 'Records processed',
);

The Counter trait tells moose that I can easily increment or decrement the value. The documentation trait is, well, you'll see that in a moment.

What I need at the end of the report is all of those counter numbers. In fact, there are a few more than what I've shown above and I'm going to be adding more in the future. I could hard-code all of them in the report, but I'm going to forget one, or someone else will add a counter and not realize it should be in the report. In this case, I thought about it and realized that this is for a command-line tool and it doesn't need to look pretty. Thus, the following method was born:

sub _print_summary {
    my $self = shift;
    return if $self->verbose;
    my @attributes =
      sort { $a->documentation cmp $b->documentation }
      grep { $_->has_applied_traits('Moose::Meta::Attribute::Native::Trait::Counter') } 
        $self->meta->get_all_attributes;
    foreach my $attribute (@attributes) {
        my $value         = $attribute->get_value($self);
        my $documentation = $attribute->documentation;
        say sprintf "%-40s %6d" => $documentation, $value;
    }
}

That fetches all attributes that are also counters and sorts hem by all attributes with documentation by their documentation (and gives me a warning if they don't have any). Then it fetches the documentation and value of each attributes and prints it out. I wind up with a summary like this:

Number of deleted persons                    20
Number of deleted sites                      13
Number of global fallbacks                    6
Number of new persons                      1931
Number of new sites                        1827
Number of updated persons                   231
Number of updated sites                      87
Records processed                          2130

It's not pretty, but then, it doesn't have to be. In the future, if I add a new counter, it will automatically show up in my summary report, properly documented.

2 Comments

It pretty amazing. I think of the macro in lispy language whenever I read Moose code like this.

Nice. I used the MOP for something similar a few months back. Net::CampaignMonitor wraps a REST API with a lot of methods and I didn't think the wrapping provided much added value.

It's someone else's module and I didn't want to break the API for others, so I created a Moosey subclass that loops over the methods to generate wrappers in the subclass that do a bit of parameter in/deflation and translation of errors into Exception::Class instances.

MOP FTW

Leave a comment

About Ovid

user-pic Have Perl; Will Travel. Freelance Perl/Testing/Agile consultant. Photo by http://www.circle23.com/. Warning: that site is not safe for work. The photographer is a good friend of mine, though, and it's appropriate to credit his work.