oleber
oleber

Reputation: 1079

Moose Role Derivation

I would like to now what is the better pattern to do what I need. I try to reduce the problem to a minimum, let me explain it step by step.

I have an interface Role like:

{
    package Likeable;
    use Moose::Role;

    requires 'likers';
    requires 'do_like';
}

After this, I need 2 Abstract Roles that semi-implement the previous interface (in this case they implement all):

{
    package Likeable::OnSelf;
    use Moose::Role;

    with 'Likeable';

    has 'likers' => ( is => 'rw', isa => 'ArrayRef' );
    sub do_like { }
}

{
    package Likeable::OnParent;
    use Moose::Role;

    with 'Likeable';

    requires 'parent';

    sub likers { shift->parent->likers(@_) }
    sub do_like { shift->parent->do_like(@_) }
}

and later I need this code to compile

{
    package OBJ::OnSelf;
    use Moose;
    with 'Likeable::OnSelf';
}

{
    package OBJ::OnParent;
    use Moose;
    with 'Likeable::OnParent';
    has 'parent' => ( is => 'rw', isa => 'Obj' );
}

foreach my $obj (OBJ::OnSelf->new, OBJ::OnParent->new(parent => OBJ::OnSelf->new)) {
    if ( $obj->does('Likeable') ) {
        $obj->do_like
    }
}

The problem seems to me that is that I'm trying to do derivation on the Moose::Role, but I have no ideia how to solve the problem correctly.

May I have your suggestions?

Upvotes: 1

Views: 155

Answers (1)

zostay
zostay

Reputation: 3995

There's no problem really with your overall role composition, but I assume you are getting an error like this:

'Likeable::OnParent' requires the method 'parent' to be implemented by 'OBJ::OnParent' at .../Moose/Meta/Role/Application.pm line 51

The problem is that has is called to create the attribute accesssor method after with is called to check for the method. (These are just subroutines being called, not actual language constructs.)

There are couple good solutions I know of. I prefer this one:

package OBJ::OnParent;
use Moose;
has 'parent' => ( is => 'rw', isa => 'Obj' );
with 'Likeable::OnParent';

Do your with statement after the attribute(s) are defined. The other option I know of is this:

package OBJ::OnParent;
use Moose;
with 'Likeable::OnParent';

BEGIN {
    has 'parent' => ( is => 'rw', isa => 'Obj' );
}

By placing your has calls in a BEGIN block, the attributes are added to the package just after use Moose is run and before with. I don't like sticking in BEGIN blocks like this, but that's mostly a personal preference.

In this particular case, though, I might suggest just changing Likeable::OnParent to such that you better specify that the parent method returns a Likeable, which will also bypass the need to change your object definitions:

package Likeable::OnParent;
use Moose::Role;

with 'Likeable';

has parent => (
    is => 'rw',
    does => 'Likeable',
    required => 1,
);

sub likers { shift->parent->likers(@_) }
sub do_like { shift->parent->do_like(@_) }

This way you have confidence that your calls to likers and do_like will succeed because the attribute must be set and it must implement the role that requires those methods and the documented contract.

Upvotes: 3

Related Questions