Search code examples
perlperl-mouse

perl Mouse set value before object returned to user


I'm trying to write a perl module using Mouse, and after the object has been initialized, but before the user makes any calls, I need to initialize object1 with the two values from object2 and object3 that the user was required to give. I tried to use Mouse's after feature to have a subroutine called after new:

package Test;
use strict;
use Mouse;

has 'object1'    => ( is => 'rw', isa => 'Any');
has 'object2'    => ( is => 'ro', isa => 'Str', required => 1); 
has 'object3'    => ( is => 'ro', isa => 'Str', required => 1); 

after 'new' => sub { 
    my ($self) = @_; 
    $self->object1(#do stuff with object2 and object3);
};

1;

However, currently I get this error:

Invalid object instance: 'Test' at lib/Test.pm line 18.

Is there a way I can initialize a value with user supplied values before the user gets the object reference returned to them?


Solution

  • Mouse is compatible with Moose. Object creation has the following phases:

    1. Arguments are passed through the BUILDARGS method if one is defined. This can munge the arguments before Moose/Mouse touch them, e.g. to provide default arguments or to accommodate other calling styles than the keyword convention.
    2. A new instance is created and the fields (declared by has) are populated.
    3. The object is now set up and ready to use (from Mouse's/Moose's perspective). You might have a different idea of a correct setup, and can perform your checks in a BUILD method. This is the point where you can also perform remaining initialization that can't be expressed with has declarations.

    So your example might become:

    use strict;
    use warnings;
    
    package Your::Class;
    use Mouse;
    
    has 'object1' => ( is => 'rw', isa => 'Any');
    has 'object2' => ( is => 'ro', isa => 'Str', required => 1); 
    has 'object3' => ( is => 'ro', isa => 'Str', required => 1); 
    
    sub BUILD { 
        my ($self) = @_; 
        $self->object1($self->object2 . $self->object3);
    };
    
    package main;
    use Test::More;
    
    # Your::Class->new(object2 => "foo", object3 => "bar");
    my $instance = new_ok('Your::Class', [object2 => "foo", object3 => "bar"]);
    is($instance->object1, "foobar");
    
    done_testing;
    

    To learn more about object construction in Moose and Moose-compatible object systems, read Moose::Manual::Construction.