Search code examples
perlmoose

How to process the argument in a Moose attribute setter before setting?


My Moose object has an attribute that is an arrayref of strings. I want to make it possible to set it to a single-element list by passing only a single string ('string'), instead of an arrayref of a single string (['string']).

has 'my_list' => (
    is      => 'rw',
    isa     => 'ArrayRef[Str]',
);

What is the proper way of solving this? Through a trigger?

I'm not sure yet if I'll need this in both the object constructor and the attribute setter, or only in the constructor.


Solution

  • The best way to approach this is by using type coercion (creating one type from another).

    Note, that it's a bad idea to coerce into standard Moose types, so we also create a subtype.

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    use feature 'say';
    
    package MyClass;
    
    use Moose;
    use Moose::Util::TypeConstraints; # defines 'subtype' and 'coerce'
    
    # Our new subtype
    subtype 'ArrayRefofStr',
      as 'ArrayRef[Str]';
    
    # Define the coercion from a string to
    # and array of strings
    coerce 'ArrayRefofStr',
      from 'Str',
      via  { [ $_ ] };
    
    has 'my_list' => (
        is      => 'rw',
        isa     => 'ArrayRefofStr', # Change to subtype
        coerce  => 1, # Turn on type coercion
    );
    
    package main;
    
    my $obj1 = MyClass->new(my_list => ['foo']);
    my $obj2 = MyClass->new(my_list =>  'bar' );
    
    say $obj1->my_list->[0];
    say $obj2->my_list->[0];