Taming the Moose: Classing up Perl attributes
Tags: perl, oop, moose, cpan
Published: 2021-11-09 15:35:35

---
title: Taming the Moose: Classing up Perl attributes
published: true
date: 2021-11-09 15:00:00 UTC
tags: Perl,OOP,Moose,CPAN
canonical_url: https://phoenixtrap.com/2021/11/09/taming-the-moose-classing-up-perl-attributes/
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r0cmwgwbcokvt9vvn3f6.jpeg
series: Taming the Moose
---

At [my work](https://www.hostgator.com/), we extensively use the [Moose](https://metacpan.org/pod/Moose) object system to take care of what would ordinarily be very tedious boilerplate [object-oriented Perl code](https://perldoc.perl.org/perlobj). In one part of the codebase, we have a family of [classes](https://metacpan.org/pod/Moose::Manual::Classes) that, among other things, map [Perl methods](https://perldoc.perl.org/perlobj#A-Method-is-Simply-a-Subroutine) to the names of various calls in a third-party API within our larger organization. Those private Perl methods are in turn called from public methods provided by [roles](https://metacpan.org/pod/Moose::Role) [consumed](https://metacpan.org/pod/Moose::Manual::Roles#CONSUMING-ROLES) by these classes so that other areas aren’t concerned with said API’s details.

Without going into too many specifics, I had a bunch of classes all with sections that looked like this:

```perl
sub _create_method    { return 'api_add' }
sub _retrieve_method  { return 'api_info' }
sub _search_method    { return 'api_list' }
sub _update_method    { return 'api_update' }
sub _cancel_method    { return 'api_remove' }
sub _suspend_method   { return 'api_disable' }
sub _unsuspend_method { return 'api_restore' }

... # etc.
```

The values returned by these very simple methods might differ from class to class depending on the API call needed, and different classes might have a different mix of these methods depending on what roles they consume.

These methods had built up over time as developers had expanded the classes’ functionality, and this week it was my turn. I decided to apply the [DRY (don’t repeat yourself) principle](https://en.wikipedia.org/wiki/Don't_repeat_yourself) and create them from a simple hash table like so:

```perl
my %METHOD_MAP = (
  _create_method    => 'api_add',
  _retrieve_method  => 'api_info',
  _search_method    => 'api_list',
  _update_method    => 'api_update',
  _cancel_method    => 'api_remove',
  _suspend_method   => 'api_disable',
  _unsuspend_method => 'api_restore',
);
```

At first, I thought to myself, “These look like private read-only [attributes](https://metacpan.org/pod/Moose::Manual::Attributes)!” So I wrote:

```perl
use Moose;

...

has $_ => (
  is       => 'ro',
  init_arg => undef,
  default  => $METHOD_MAP{$_},
) for keys %METHOD_MAP;
```

Of course, I’d have to move the classes’ [`with`](https://metacpan.org/pod/Moose::Manual::Roles#CONSUMING-ROLES) statements after these definitions so the roles they consume could “see” these runtime-defined attributes. But some of the methods used to read these are class methods (e.g., called as `ClassName->foo()` rather than `$object->foo()`), and Moose attributes are only set after the construction of a class instance.

Then I thought, “Hey, Moose has a [MOP (meta-object protocol)](https://metacpan.org/dist/Moose/view/lib/Moose/Manual/MOP.pod)! I’ll use that to generate these methods at runtime!”

```perl
my $meta = __PACKAGE__ ->meta;

while (my ($method, $api_call) = each %METHOD_MAP) {
    $meta->add_method( $method => sub {$api_call} );
}
```

The [`add_method` documentation](https://metacpan.org/pod/Class::MOP::Class#%24metaclass-%3Eadd_method(%24method_name,-%24method)) “strongly encourage[s]” you to pass a metamethod object rather than a code reference, though, so that would look like:

```perl
use Moose::Meta::Method;

my $meta = __PACKAGE__ ->meta;

while (my ($method, $api_call) = each %METHOD_MAP) {
    $meta->add_method( $method = Moose::Meta::Method->wrap(
      sub {$api_call}, __PACKAGE__ , $meta,
    );
}
```

This was getting ugly. There had to be a better way, and fortunately there was in the form of [Dave Rolsky](https://houseabsolute.com/)’s [MooseX::ClassAttribute](https://metacpan.org/pod/MooseX::ClassAttribute) module. It simplifies the above to:

```perl
use MooseX::ClassAttribute;

class_has $_ => (
  is      => 'ro',
  default => $METHOD_MAP{$_},
) for keys %METHOD_MAP;
```

Note there’s no need for `init_arg => undef` to prevent setting the attribute in the constructor. Although they’re still Moose attributes, they act like class methods so long as the class consumes the roles that require them after the attribute definitions.

Lastly, if we were using [Moo](https://metacpan.org/pod/Moo) as a lightweight alternative to Moose, I could have instead selected [Toby Inkster](https://toby.ink/)’s [MooX::ClassAttribute](https://metacpan.org/pod/MooX::ClassAttribute). Although it has [some](https://metacpan.org/pod/MooX::ClassAttribute#CAVEATS) [caveats](https://metacpan.org/pod/Method::Generate::ClassAccessor#CAVEATS), it’s pretty much the only alternative to our initial class method definitions as Moo [lacks a meta-object protocol](https://metacpan.org/pod/Moo#META-OBJECT).

The lesson as always is to [check CPAN](https://phoenixtrap.com/2021/10/12/read-the-fantastic-manual-how-to-get-the-most-out-of-perl-documentation/#metacpan) (or the appropriate mix of your language’s software repository, forums like [Stack Overflow](https://stackoverflow.com/), etc.) for anything that could conceivably have application outside of your particular circumstances. Twenty-five years into my career and I’m still leaping into code without first considering that someone smarter than me has already done the work.