Alois Mahdal
Alois Mahdal

Reputation: 11243

Instantiating mandatory attributes during construction

Suppose I want a class like this:

package Restraint;
use Moose;

has ball => (
    is   => 'ro',
    does => 'Heavy',
);

has chain => (
    is   => 'ro',
    does => 'Lockable',
);

has ball_class => (
    is   => 'ro',
    isa  => 'Str',
);

has chain_class => (
    is   => 'ro',
    isa  => 'Str',
);

1;

Now I want to instantiate the class like:

my $r = Restraint->new(
    ball_class = 'Ball',
    chain_class = 'Chain',
);

both arguments being mandatory.

I also want to instantiate both ball_class and chain_class during construction and assign them to corresponding attributes so that finally I can e.g. $r->ball->lift or $r->chain->lock etc.

How should I do that?

Upvotes: 2

Views: 147

Answers (2)

Richard Huxton
Richard Huxton

Reputation: 22893

Just mark them both "required" - see Moose::Manual::Attributes

That's not going to do anything for "ball" or "chain" though. There's no connection.

You could set up ball/chain with lazy builders which would reference your classnames (see the Moose Manual again).

I'd probably just make the objects required and pass them in directly though:

my $r = Restraint->new(
  ball  => CannonBall->new(...),
  chain => IronChain->new(...)
);

OK - you don't want to pass in objects. In that case you want lazy builders. Mark the ball as lazy and give it a builder and Moose will call the builder when the ball is first used.

http://metacpan.org/pod/Moose

http://metacpan.org/pod/Moose::Manual::Attributes

http://metacpan.org/pod/Moose::Cookbook::Basics::Recipe3

Do take the time to read the moose documentation - there's a lot of it, but it's quite well written and covers plenty of features. Lots of examples.

package Restraint;
use Moose;

has ball => (
    is => 'ro',
    does => 'Heavy',
    lazy => 1,
    builder => '_build_ball'
);

has ball_class => (
   is => 'ro',
   required => 1
);

sub _build_ball {
   my $self = shift;
   my $ball_class = $self->ball_class;
   return $ball_class->new();
}

Upvotes: 3

Alois Mahdal
Alois Mahdal

Reputation: 11243

Using BUILD for instantiation while setting required only for *_class attributes seems to work to some extent, except that:

  • I had to make ball and chain read-write

Code:

package Restraint;
use Moose;

has ball => (
    is   => 'rw',
    does => 'Heavy',
);

has chain => (
    is   => 'rw',
    does => 'Lockable',
);

has ball_class => (
    is   => 'ro',
    isa  => 'Str',
    required => 1,
);

has chain_class => (
    is   => 'ro',
    isa  => 'Str',
    required => 1,
);

sub BUILD {
    my $self = shift;

    my $ball = $self->ball_class->new();
    $self->ball( $ball );
    my $chain = $self->chain_class->new();
    $self->chain( $chain );
}

1;

It's not a big sacrifice for now, but I still wonder if there is more correct way, though.

Upvotes: 1

Related Questions