Enumerations in Moose

This is a very old article. It has been imported from older blogging software, and the formatting, images, etc may have been lost. Some links may be broken. Some of the information may no longer be correct. Opinions expressed in this article may no longer be held.

It’s quite a common pattern in object-oriented programming to have an attribute which takes a string as its value, but which only has a small number of valid values. For example:

 package Shirt;
 use Moose;
 
 # "S", "M", "L", or "XL"
 has size => (is => 'ro', isa => 'Str', required => 1);

This offers no protection against invalid string values.

 # No exception is thrown
 my $shirt = Shirt->new(size => "LX");

Moose can do some validation for you:

 package Shirt;
 use Moose;
 use Moose::Util::TypeConstraints;
 
 enum ClothesSize => [qw/ S M L XL /];
 
 has size => (is => 'ro', isa => 'ClothesSize', required => 1);

That protects us from one class of error. Here’s another:

 if ( $shirt->size eq "LX" ) {
    $price += 2.50;
 }

Oh dear, that conditional is never going to be true, is it? How can we protect against that sort of error?

 package Shirt;
 use Moose;
 use Moose::Util::TypeConstraints;
 
 enum ClothesSize => [qw/ S M L XL /];
 
 has size => (is => 'ro', isa => 'ClothesSize', required => 1);
 
 sub is_S  { shift->size eq "S"  }
 sub is_M  { shift->size eq "M"  }
 sub is_L  { shift->size eq "L"  }
 sub is_XL { shift->size eq "XL" }

Now our price calculation can be:

 if ( $shirt->is_LX ) {
    $price += 2.50;
 }

To err is human: we’ve still misspelt it, but at least an exception will be thrown.

Now our simple little string attribute has become a lot more complex. How can we bring it back under control?

MooseX::Enumeration

First, we install MooseX::Enumeration. Then we use it:

 package Shirt;
 use Moose;
 has size => (
    traits   => ['Enumeration'],
    is       => 'ro',
    enum     => [qw/ S M L XL /],
    handles  => 1,
    required => 1,
 );

This does everything from the previous example, including defining is_S, is_M, is_L, and is_XL methods.

The Bare Necessities

Now, here’s a trick. It may be that because of our wonderful new is_* methods, we never want to call $shirt->size again. In fact, it’s possible to tell Moose to never create that accessor method:

 package Shirt;
 use Moose;
 has size => (
    traits   => ['Enumeration'],
    is       => 'bare',
    enum     => [qw/ S M L XL /],
    handles  => 1,
    required => 1,
 );

Now the shirt size can be set through the constructor as usual, but can only be queried via the is_* interface.