Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to automatically coerce parameters passed to delegated methods (from the Array trait) using Moose/MooseX::Declare for Perl?

I'm creating a class which will contain a list of IP addresses, as Net::IP objects.

I've wrapped the Net::IP object as a subtype (IPAddress), and defined a coercion from a string to IPAddress. Then I've added an attribute to the class called ip_list with the type ArrayRef[IPAddress], and delegated to the push method of the Array trait.

use MooseX::Declare;
use Moose::Util::TypeConstraints;

use Net::IP;

subtype 'IPAddress'
    => as 'Object'
    => where { $_->isa('Net::IP') };

coerce 'IPAddress'
    => from 'Str'
    => via { Net::IP->new( $_ ) };

class IPs {

    has 'ip_list' => ( traits  => ['Array'],
                       isa    => 'ArrayRef[IPAddress]',
                       is     => 'rw',
                       coerce => 1,
                       auto_deref => 1,
                       default => sub { [] },
                       handles => {
                           add_ip    => 'push'
                       }
                       );

}

However if I try to call the delegated method like so:

my $o = IPs->new();
$o->add_ip( '192.168.0.1' );

I get the error "Value SCALAR(0x8017e8) did not pass container type constraint 'IPAddress' at ..."

So obviously the parameter to add_ip is not being coerced.

Is it possible to do what I'm attempting, or should I just do all this manually? I've trawled through the Moose manuals but I've not seen anything that would indicate either way, but I am probably missing something.

like image 371
Paul Leader Avatar asked Oct 20 '10 14:10

Paul Leader


1 Answers

Unfortunately Moose does not chain coercions (it would be really complicated to parse these internally and figure out what the "right thing to do" is in an automatic fashion), so you need to define the chain yourself:

use Net::IP;

class_type 'Net::IP';

coerce 'Net::IP'
    => from 'Str'
    => via { Net::IP->new( $_ ) };

subtype 'ArrayRefOfIPAddresses'
    => as 'ArrayRef[Net::IP]';

coerce 'ArrayRefOfIPAddresses'
    => from 'ArrayRef[Str]'
    => via { [ map { Net::IP->new($_) } @$_ ] };

coerce 'ArrayRefOfIPAddresses'
    => from 'Str'
    => via { [ Net::IP->new($_) ] };

coerce 'ArrayRefOfIPAddresses'
    => from 'Net::IP'
    => via { [ $_ ] };

class IPs {

    has 'ip_list' => ( traits  => ['Array'],
                       isa    => 'ArrayRefOfIPAddresses',
                       # ... rest of declaration as before
                     );

}

PS. since you are using the Array native delegation trait, I would recommend you avoid auto_deref - add a handler instead:

has ip_list => (
    is => 'bare',
    # ...
    handles => {
        # ...
        ip_list => 'elements',
    },
);
like image 170
Ether Avatar answered Oct 17 '22 00:10

Ether