Dynamically modifying methods in Moo using the published Moo API

194 Views Asked by At

I'm trying to stick to the published API to dynamically modify methods in Moo, and have not come up with a general solution.

First off, some code:

package R1 {
    use Moo::Role;
    sub r1 { say __PACKAGE__ }
}

package C1 {
    use Moo;
    sub c1 { say __PACKAGE__ }
}

use Scalar::Util qw[ blessed ];
use Moo::Role ();

my $c = C1->new;
Moo::Role->apply_roles_to_object( $c, 'R1' );

The role application will illustrate a failure in one approach.

I've tried two approaches.

The first uses Class::Method::Modifiers:

use Class::Method::Modifiers qw[ install_modifier ];
install_modifier( blessed( $c ), 
                  before => r1 =>
                  sub { say "BEFORE r1" }
                );
$c->r1;

and works fine:

% perl test.pl
BEFORE r1
R1

The code for Moo's internal _install_modifier subroutine is very similar, but also performs additional Moo specific actions, so this approach is not exactly equivalent.

The next approach I tried was to directly use the before modifier available to $c, and thus get the additional Moo special sauce:

$c->can('before')->( r1 => sub { say "BEFORE r1" } );
$c->r1;

But...

% perl test.pl
The method 'r1' is not found in the inheritance hierarchy for class C1 at [...]/lib/site_perl/5.28.0/Class/Method/Modifiers.pm line 42.
        Class::Method::Modifiers::install_modifier("C1", "before", "r1") called at /[...]/lib/site_perl/5.28.0/Moo/_Utils.pm line 44
        Moo::_Utils::_install_modifier("C1", "before", "r1", CODE(0x5590bb800360)) called at [...]/lib/site_perl/5.28.0/Moo.pm line 84
        Moo::before("r1", CODE(0x5590bb800360)) called at test.pl line 25

It seems that modifiers were generated for the original C1 class, and not updated when the R1 role was applied. The following egregious hack "fixes" that:

use Import::Into;
Moo->import::into( blessed $c );

$c->can('before')->( r1 => sub { say "BEFORE r1" } );
$c->r1;

which results in:

% perl test.pl
BEFORE r1
R1

So, is there a means of achieving my goal using only the published Moo API?

Thanks!

1

There are 1 best solutions below

6
Grinnz On

You can modify methods by just applying another role (it doesn't even have to be Moo::Role unless you are dealing with attributes):

use Role::Tiny;
before r1 => sub { say "BEFORE r1" };

Just make sure you apply this role after the one that composes the r1 method, or include a dummy sub r1 {} in the role (if one already exists it will be ignored).