http://www.perlmonks.org?node_id=1015201

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

Hi there,

I am new to Moose and I could use a little help on how to fix this.

FYI, I completely agree with the error, just need suggestions on how to fix the code.

The code models somebody's library, with books that the owner has read and books that he has not. At the beginning, all books are meant to be 'unread', and as he starts reading, he moves them into the 'read' pile.

LibraryTest.pm
package LibraryTest; use Moose; use Book; has 'unread_books' => (isa => 'ArrayRef[Book]', is =>'rw' ); has 'read_books' => (isa => 'ArrayRef[Book]', is =>'rw' );

Main.pl - already have a ref to a list of books saved in $allbooks. Please note that the second attribute, read_books, is not initialized

my $Libr = LibraryTest->new(unread_books => $allbooks);

....he reads the first book and wants to move it from the unread pile to the read pile...

$Libr->MoveBook($Libr->PopBook);

This is where disaster strucks .. the second attribute of the object is undef... how do I initialize it with a reference to an empty array of Book?

Or maybe it's better to modify the constructor somehow so that for when the second attribute is not explicitly given ?

Many thanks for any suggestion :)

Replies are listed 'Best First'.
Re: Can't call method "MoveBook" on an undefined value
by tobyink (Canon) on Jan 24, 2013 at 19:52 UTC

    You should use a default or builder for the attributes. Defaults work like this:

    package LibraryTest; use Moose; use Book; has unread_books => ( is =>'rw', isa => 'ArrayRef[Book]', default => sub { [] }, ); has read_books => ( is =>'rw', isa => 'ArrayRef[Book]', default => sub { [] }, );

    Builders work like this:

    package LibraryTest; use Moose; use Book; has unread_books => ( is =>'rw', isa => 'ArrayRef[Book]', builder => '_build_unread_books', ); has read_books => ( is =>'rw', isa => 'ArrayRef[Book]', builder => '_build_read_books', ); sub _build_unread_books { return []; } sub _build_read_books { return []; }

    Defaults are very convenient to define, but builders are arguably a better way to do things: the builder is a regular method, so subclasses can easily override it, which is handy.

    Builders are a very handy thing when you get used to them - especially when you use them in conjunction with Moose's lazy attribute initialisation feature.

    package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name
Re: Can't call method "MoveBook" on an undefined value
by davido (Cardinal) on Jan 24, 2013 at 18:45 UTC

    You have a lot of options: BUILD, BUILDARGS, or possibly even "lazy => ..., builder => ..."

    At first blush I would say it could be a job for a BUILD routine, but would prefer to see more before saying for certain. If possible I would favor doing it in the attribute's declaration (again, possibly builder).

    See Moose::Manual::Construction, and Moose::Manual::Attributes for more explanation.


    Dave

Re: Can't call method "MoveBook" on an undefined value
by kcott (Archbishop) on Jan 24, 2013 at 18:59 UTC

    G'day bgu,

    I'd suggest using the default option with the has attribute - see Moose - EXPORTED FUNCTIONS for details.

    Side issue: it looks like you're missing a $ before the second Libr in:

    $Libr->MoveBook(Libr->PopBook);

    -- Ken

Re: Can't call method "MoveBook" on an undefined value
by thargas (Deacon) on Jan 25, 2013 at 15:50 UTC
    Hmm. The "Can't call method "MoveBook" on an undefined value" error would tend to indicate that the LibraryTest->new() returned undef. I.E. $Libr is undef.