pileofrogs has asked for the wisdom of the Perl Monks concerning the following question:

Greetings, ye Monks!

I'm using Moose for the first time, and I'm a little baffled by something.

I have an object attribute that is an arrayref. I'd like the writer for that attribute to append a value if I call the writer with a single valid value. How would I do that? Coercion doesn't look right, because I don't want to change the value, I want to change how the value is added. I've tried setting 'accessor' to a custom sub, but it didn't even get called. So, I'm asking you monks for help.

Here's an example-ish illustration of what I want.

Package Wibble; use Moose; has 'thingies' => ( is => 'rw', isa => 'ArrayRef' ); ## Things I don't understand yet go here... Package main; use Wibble; my $w = Wibble->new( thingies => [ 'this', 'that' ]); print join(", ",$w->thingies())."\n"; # prints "this, that" # here's the bit that I want to make work $w->thingies('another'); print join(", ",$w->thingies())."\n"; # prints "this, that, another"

I hope that makes sense.

Thanks!

--Pileofrogs

Replies are listed 'Best First'.
Re: Moose Accessor Question
by rhesa (Vicar) on Feb 06, 2009 at 23:17 UTC
    You're looking for MooseX::AttributeHelpers:
    package Wibble; use Moose; use MooseX::AttributeHelpers; has 'thingies' => ( metaclass => 'Collection::Array', is => 'ro', isa => 'ArrayRef[Str]', default => sub { [] }, provides => { push => 'add_thingies', elements => 'list_thingies', }, );
    This adds wrapper methods into your class to manipulate the underlying arrayref. I've given them sensible names too: add_thingies to push new elements to the end of the array, and list_thingies to return the list of values.

    Now you can add additional items like so:

    my $w = Wibble->new( thingies => [ 'this', 'that' ]); print join(", ",$w->list_thingies())."\n"; # prints "this, that" # here's the bit that we just made work $w->add_thingies('another'); print join(", ",$w->list_thingies())."\n"; # prints "this, that, another"
    The specific methods you can provide for ArrayRef attributes are listed in MooseX::AttributeHelpers::MethodProvider::List and MooseX::AttributeHelpers::MethodProvider::Array.

    I can also recommend the Moose::Manual. It's a nice overview of how Moose works, and describes some of the commonly used extensions.

      ++

      That's what I'm looking for! Thanks! I've been reading the manual, but I hadn't found that answer. Thanks again!

      Having “been there, done that” very recently... let me toss-in what it took me a very long time to figure out. (For whatever it may be worth...)

      (1) The methods (like "add_thingies") are iautomagically created for you/i by Moose, and they are attached to the iobject/i. In effect, they serve to iconceal/i from any of the object's clients that the underlying data structure exists.

      (2) Sometimes it's overkill. Sometimes List::Util and so-forth just works best. Also, I've had my buttsky saved dozens of times now by Data::Util's instance() function... The very-nicest thing about Moose is, “it's still Perl.”

Re: Moose Accessor Question
by stvn (Monsignor) on Feb 07, 2009 at 03:26 UTC

    I second rhesas recommendation for MooseX::AttributeHelpers, but for complete-ness sake I will also point out Moose::Autobox with which you could do this:

    Package Wibble; use Moose; has 'thingies' => ( is => 'rw', isa => 'ArrayRef' ); Package main; use Wibble; use Moose::Autobox; my $w = Wibble->new( thingies => [ 'this', 'that' ]); print join(", ",$w->thingies())."\n"; # prints "this, that" # here's the bit that I want to make work $w->thingies->push('another'); print join(", ",$w->thingies())."\n"; # prints "this, that, another"
    And it would do what you want as well.

    -stvn
Re: Moose Accessor Question
by zwon (Abbot) on Feb 06, 2009 at 22:49 UTC

    Arghhh... Your code doesn't work! I fixed it as follows and there's no behavior you described:

    package Wibble; use Moose; has 'thingies' => ( is => 'rw', isa => 'ArrayRef' ); package main; my $w = Wibble->new( thingies => [ 'this', 'that' ] ); print join( ", ", @{ $w->thingies() } ) . "\n"; $w->thingies('another'); print join( ", ", @{ $w->thingies() } ) . "\n"; __END__ this, that Attribute (thingies) does not pass the type constraint because: Valida +tion failed for 'ArrayRef' failed with value another at (eval 69) lin +e 7 Wibble::thingies('Wibble=HASH(0x1274b38)', 'another') called at 74 +2018.pl line 10

    Update: and if you use $w->thingies(['another']); the last statement prints just 'another'.

      Sorry, I must not have been clear. It's not supposed to be an example of working code. It's an example of how I'd like my code to be able to work.

      I assume I'd have to do something in the definition of Wibble to make it work and that something is what I'm looking for.

        Ooops... My bad, misread.

Re: Moose Accessor Question
by FunkyMonk (Bishop) on Feb 06, 2009 at 23:16 UTC
    I'd write a separate method to add thingies:
    package Wibble; use Moose; has 'thingies' => (is => 'rw', isa => 'ArrayRef' default => sub { [] } +); sub add_thingies { my $self = shift; push @{ $self->thingies }, ref $_ ? @$_ : $_ for @_; } package main; my $w = Wibble->new( thingies => [ 'this', 'that' ] ); print join( ", ", @{ $w->thingies() } ) . "\n"; # this, that $w->add_thingies('another'); print join( ", ", @{ $w->thingies() } ) . "\n"; # this, that, another $w->add_thingies('and', [qw/some more/]); print join( ", ", @{ $w->thingies() } ) . "\n"; # this, that, another, + and, some, more


    Unless I state otherwise, all my code runs with strict and warnings