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

1

5

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.

Moving answered 20/10, 2010 at 14:57 Comment(0)
S
7

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',
    },
);
Scrimpy answered 20/10, 2010 at 16:27 Comment(5)
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 :/Moving
@Paul: I'll take a look in ~2 hours to see what happened.. :(Scrimpy
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.Moving
@Paul: update: here's where I'm at so far: 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.)Scrimpy
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.Moving

© 2022 - 2024 — McMap. All rights reserved.