Reputation: 1079
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
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