Enumerations in Moose
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.
Nice! I wouldn't mind at all if this became Moose::Meta::Attribute::Native::Trait::Enum. Nothing would break, and the entire ecosystem can benefit immediately.