Search code examples
perlattributesbuildermoose

How to call method within builder


I have a class with an attribute set up as follows:

has _data => (
    is => 'ro',
    lazy => 1,
    builder => '_load',
);

sub _load {
    my $self = shift;
    return retrieve $self->_file;
}

However I now want to call a method already defined on the class before returning the data.

In old-school Perl OO, I'd be doing something like this:

sub _load {
    # Assuming laziness is implemented somewhere else.
    my $self = shift;
    $self->{_data} = retrieve $self->_file;
    $self->refresh;  # which does something with $self->{_data}
    return $self->{_data};
}

But I can't figure out a 'clean' way to do this in Moose.

I've considered the following, but think they are quite ugly, and that there must be a better way of doing this.

  • If I make _data read-write, I could potentially write the data to the accessor, call the method then return the value from the accessor for Moose to write back to the accessor.
  • If I turn it into a plain old method then I'd have to define another attribute, say _raw_data, store the data in there, modify refresh() to use that attribute, and everything else uses _data().
  • Violate encapsulation and access the underlying $self->{_data} directly.

I tried an after '_load' => \&refresh;, but that just created an endless loop.


Solution

  • This would be a nice use of triggers:

    has _data => (
       is      => 'ro',
       lazy    => 1,
       builder => '_load',
       trigger => sub { shift->refresh },
    );
    

    Except that triggers don't work on default/built values - only on values passed to the constructor explicitly, or passed to a writer/accessor method. Sad face. :-(

    One solution would be to rewrite your refresh method so that instead of operating on $self->_data, it can accept a parameter (perhaps falling back to operating on $self->_data if no parameter is given.

    sub _load {
       my $self = shift;
       my $tmp = retrieve $self->_file;
       $self->refresh($tmp);
       return $tmp;
    }
    
    sub refresh {
       my $self = shift;
       my $data = scalar(@_) ? $_[0] : $self->_data;
       # do something with $data
    }