views:

70

answers:

1

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.

+7  A: 

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',
    },
);
Ether
Ah, that makes sense.
Paul Leader
Hrrrm, that doesn't seem to be working the way I'd expect. If I use Data::Dumper to inspect the resulting object, ip_list is a shown as an array of strings. So it isn't doing the coercion _or_ the typechecking :/
Paul Leader
@Paul: I'll take a look in ~2 hours to see what happened.. :(
Ether
Thanks, I'll double check that I didn't do something silly. I'd have done so today but I've been up to my eyeballs at work.
Paul Leader
@Paul: update: here's where I'm at so far: http://paste.scsys.co.uk/53787 .. this might be an issue with the recent reimplementation of native attrs in Moose 1.16. (Update2: yes it's a bug.)
Ether
@Paul: filed as https://rt.cpan.org/Ticket/Display.html?id=62351
Ether
Ether, thank you so much, I thought I was going mad :) I'll take a slightly different approach for the moment, and keep an eye on things.
Paul Leader