Search code examples
perloopmoose

Creating attribute defaults by calling a wrapped object


I have WrapperClass object that has an InnerClass object as an attribute. The InnerClass object has a weight attribute. My WrapperClass object also has a weight attribute and I want its default value to be whatever the value of the InnerClass object's weight attribute is.

#!/usr/bin/perl
package InnerClass;
use Moose;

has 'weight' => (
    is => 'rw',
);

package WrapperClass;
use Moose;

has 'wrapped' => (
    is => 'rw',
    lazy => 1,
    default => sub {InnerClass->new(weight => 1)},
);

has 'weight' => (
    is => 'rw',
    default => sub {
        my $self = shift;
        $self->wrapped->weight()
    },
    lazy => 1,
);

The code above works, but in reality InnerClass has many attributes which WrapperClass needs to do the same thing for. Ideally I would do something like this when I'm writing WrapperClass:

use Moose;

has 'wrapped' => (
    is => 'rw',
);

my @getDefaultsFromWrappers
    = qw(weight height mass x y z label); # etc ...

foreach my $attr (@getDefaultsFromWrappers) {
    has $attr => (
        is => 'rw',
        default => sub {
            # Somehow tell the default which attribute
            # it needs to call from wrapped object?
            my $self = shift;
            $self->wrapped->???()
        },
        lazy => 1,
    );
}

However, there is no way of passing an argument to a default or builder to tell it which attribute it is building. I've considered using caller but this seems like a hack.

Does anyone know how I could accomplish this style of attribute declaration or is it a case of declaring each attribute and its default separately?


Solution

  • You can use $attr where your question marks are because it is still in scope when you declare the attributes.

    foreach my $attr (@getDefaultsFromWrappers) {
        has $attr => (
            is      => 'rw',
            default => sub { shift->wrapped->$attr() },
            lazy    => 1,
        );
    }
    

    The following is a possible alternative, which you might want to use if your attribute declarations are not uniform:

    has weight => (
        is      => 'rw',
        isa     => 'Num',
        default => _build_default_sub('weight'),
        lazy    => 1,
    );
    
    has label => (
        is      => 'rw',
        isa     => 'Str',
        default => _build_default_sub('label'),
        lazy    => 1,
    );
    
    sub _build_default_sub {
        my ($attr) = @_;
        return sub { shift->wrapped->$attr };
    }