2

I want to define an array as attribute of a class and fill it with some data when the class is instantiated.

I thought it would be possible to use a $self->attribute->set($id, $value) method in order to set an element on a given index. At least that's what I understood from the Moose documentation.

But when I try

use Data::Dumper qw( Dumper );
use Moose;

has cells => (
    is => 'rw',
    traits  => ['Array'],
    isa     => 'ArrayRef',
    default => sub { [] },
);

my $app = __PACKAGE__->new();
$app->cells->set($_, $_) for 0..3;
print(Dumper($app->cells));

I get

Can't call method "set" on unblessed reference

How can I do I make set work?

2
  • I wanted to ask how to use the set() method for the attribute I changed to post, hopefully it is clear now. Commented Dec 15, 2012 at 22:07
  • 1
    set is not a directly callable method. It's something that looks like a method to handles. You don't get to call it yourself. Commented Dec 17, 2012 at 20:08

3 Answers 3

5
use Data::Dumper qw( Dumper );
use Moose;

has cells => (
    is => 'rw',
    traits  => ['Array'],
    isa     => 'ArrayRef',
    default => sub { [] },
    handles => {                   # <---
       set_cell => 'set',          # <---
    },                             # <---
);

my $app = __PACKAGE__->new();
$app->set_cell($_, $_) for 0..3;   # <---
print(Dumper($app->cells));

Despite claims to the contrary in the comments, it works fine in BUILD too.

use Data::Dumper qw( Dumper );
use Moose;

has cells => (
    is => 'rw',
    traits  => ['Array'],
    isa     => 'ArrayRef',
    default => sub { [] },
    handles => {
       set_cell => 'set',
    },
);

sub BUILD {
    my ($self) = @_;
    $self->set_cell($_, $_) for 0..3;
}

my $app = __PACKAGE__->new();
print(Dumper($app->cells));
Sign up to request clarification or add additional context in comments.

8 Comments

No, I'm sorry, this does not solve my problem. I wrote a demonstration script for the single purpose of demonstrating how I was doing it and what went wrong. Unfortunately, this script does not exists anymore because my original post was altered... Applying the proposal of this answer still yields the error when setting the cells in the BUILD method.
@capfan ikegami's answer works for me. If you're doing something more involving BUILD please edit your post to show us.
@capfan, It works fine with your original code if you apply both changes I showed (handles and set_cell), and if you stop clobbering the cells you set by doing $self->cells(\@cells);.
Aha, now I see. Thanky for all the comments and explanations. As said, a complete (but minimal) code sample of a script including the BUILD method was given, but edited. Now, ppl giving answers have to deal with it. Schwern's answer helped the most. So, who gets the credit?
The code was far from minimal. As you can see, there's nothing special about BUILD. A huge distraction should should have eliminated before you even asked the question. You didn't, and you keep bringing it up, because you didn't.
|
3

@ikegami's answer is correct. Here's an expanded explanation.

The issue is with this line:

$app->cells->set($_, $_) for 0..3;

To make it more clear, let's get rid of the loop and expand it.

my $cells = $app->cells;
$cells->set(0, 0);

$app->cells returns a plain array ref, just as you defined in your has cells call. So $cells is nothing but a plain, unblessed array ref. You can't call any methods on that, that's (unfortunately) how Perl works unless you get into autobox.

Note in the documentation that all the methods provided are called on the object, not on the thing returned by the attribute. Also, and the docs are not clear on this point, each method has the name of the attribute appended to it. You don't call count you call count_cells. You don't call set you call set_cells.

$app->set_cells(0, 0);
print $app->count_cells;

Comments

3

You don't do that. There is no set method on $app->cells because $app->cells is an array reference, not an object, so it doesn't have methods. Applying the Array native trait to an attribute doesn't make an arrayref into an object; it just makes that attribute able to answer handles requests by using methods provided by the trait instead.

If you want to be able to call methods on an unblessed array reference, you might consider using Moose::Autobox, but if you take the Law of Demeter into consideration you'll find that native traits let you build a much more robust interface than letting users directly poke at your data members does.

1 Comment

Good explanation. I couldn't figure out a good way of saying it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.