views:

56

answers:

2

I have a Moose object module that should accept a relatively large data structure (ds) as one of its constructor arguments. It is used to calculate some of the object's attributes. However, I do not wish to store ds itself as an attributes -- it is only needed during the construction of the object.

I thought of using BUILDARGS but then I'm not sure how to define that ds is a required argument.

How can I work around this?

+2  A: 

You could use either BUILD or BUILDARGS for this. It's hard to say which would be better without knowing more about what you're trying to do, but I'd guess BUILD would be the better choice.

sub BUILD {
    my $self = shift;
    my $args = shift;

    my $ds = $args->{ds} or confess "Argument (ds) is required";

    $self->some_attr($ds->{...});
    $self->other_attr($ds->{foo}[3]);
    ...
} # end BUILD

If you want Moose to check the type and ensure it's present, you have to make it an attribute. But you can clear it in the BUILD method after you use it.

 has 'ds' => (
     is       => 'ro',
     isa      => 'SomeType',
     required => 1,
     clearer  => '_clear_ds',
 );

sub BUILD {
    my $self = shift;
    my $args = shift;

    my $ds = $self->ds;
    $self->_clear_ds;

    $self->some_attr($ds->{...});
    $self->other_attr($ds->{foo}[3]);
    ...
} # end BUILD

You could name the reader method something else (like _ds) if you wanted to.

cjm
This way I have to (a) manually check the required arg is indeed given (as you did), and (b) manually verify the type of the arg is correct. These are two of the things I really like in Moose. Can't I use Moose strengths in this situation, too?
David B
+1 Thank cjm. What about serialization of such objects (using KiokuDB)? When the object is loaded, won't `BUILD` be executed and cause trouble?
David B
Yes, Moose objects, when reserialized/thawed, go through the standard instantiation steps. Ran into this a couple weeks ago. Off the top of my head, you could set an attribute such as hasBeenInsantiated to 1, and check if it was passed into BUILD before using that data to compute the other attributes.
SoloBold
+2  A: 

I'd be inclined to have a constructor that takes only the calculated values derived from your data structure. Then use a different method to take limited params plus the data structure as args.

sub generate_foo_from_ds {
    my $class = shift; 
    my %arg = @_;

    my $ds = $arg{foo_data};

    # Get attributes from args
    my %attrib;
    for (qw(foo bar baz ) {
        croak "Attrib '$_' is required" unless exists $arg{$_};
        $attrib{$_} = $arg{$_};
    }

    # calculate some more attributes here.
    $attrib{griz} = $ds->{whee} * $ds->{whoosh} / $ds->{whiz}[12];

    my $foo = $class->new( %attrib );

    return $foo;
}

Then make your objects like so:

my $foo = Foo->generate_foo_from_ds( foo_data => $bar, foo => 1, bar => 2, baz => 2 );

Now you don't have to worry about weird serialization issues or BUILDARGS or even BUILD. You have a simple method and that is all.

daotoad
This is a nice solution. Now if only my perl coworkers would quit their uneasiness about class methods.
SoloBold
Nothing wrong with class methods. Especially not with those that create instances of the class. `generate_foo_from_ds` is just a special purpose constructor. BTW, I did notice an error in my code that I have now fixed. I blame a caffeine deficiency.
daotoad