Question

I have a Moo(se)[0] class with a number of methods which have the exact same type of "guard statement" at the top. Instead of writing the same code several of times I figured I could put the statement in a "before" method modifier, and that works perfectly. Unless this class is subclassed, because then the "before guard" is never called.

package Foo;
use feature 'say';
use Moose;

has '_init' => (
  is      => 'rw',
  isa     => 'Bool',
  default => 0
);

sub init {
  shift->_init(1);
}

sub method {
  say "in Foo::method";
}

before method => sub {
  my $self = shift;
  warn "==> Foo is not initialized\n" unless $self->_init;
};


package Bar;
use feature 'say';
use Moose;
extends 'Foo';

sub method {
  say "in Bar::method";
}


package main;
use feature 'say';

my $foo = Foo->new;
say "foo the wrong way:";
$foo->method;

say "foo the right way:";
$foo->init;
$foo->method;

my $bar = Bar->new;
say "bar the wrong way:";
$bar->method;

Output is then (with some added new lines):

foo the wrong way:
==> Foo is not initialized
in Foo::method

foo the right way:
in Foo::method

bar the wrong way:
in Bar::method

I assume this behaviour is by design, but is there any (nice) way to make sure all subclasses also inherit the "before" method modifier/guard statement? Or is there a different way to accomplish this (I suspect it's a rather common construct). Note that an exception will be thrown in the real guard statement, but a "warn" is much simpler in example code.

[0] I prefer to use Moo because I don't use any features requiring MOP, but both Moo and Moose works the exact same way in this matter.

Edit using Roles.

If a add a Role for this (as suggested by tobyink), and add another method for making things a bit more 'real life', I get a peculiar result.

package Warning::NotInit;
use feature 'say';
use Moose::Role;

has '_init' => (is => 'rw', isa => 'Bool', default => 0);

before qw/ m1 m2 / => sub {
  my $self  = shift;
  my $class = ref($self);
  warn "==> $class is not initialized\n" unless $self->_init;
};

package Foo;
use feature 'say';
use Moose;
with 'Warning::NotInit';

sub init { shift->_init(1) }
sub m1   { say "in Foo::m1" }
sub m2   { say "in Foo::m2" }

package Bar;
use feature 'say';
use Moose;
extends 'Foo';
with 'Warning::NotInit';

sub m1 { say "in Bar::m1" }


package main;
use feature 'say';

When calling the not overridden method in the subclass, the before method is called twice.

my $bar = Bar->new;
say "bar the wrong way:";
$bar->m1;
$bar->m2;

Output:

bar the wrong way:
==> Bar is not initialized
in Bar::m1

==> Bar is not initialized
==> Bar is not initialized
in Foo::m2

Why is it called twice?

Was it helpful?

Solution

Yes, that's not how method modifiers work. The before modifier becomes part of the method itself. When you override the method in your subclass, you're overriding the entirety of the superclass' behaviour - that is, you're overriding the method modifier as well.

You could solve this by factoring out the method modifier into a role which can be applied to each class, like this:

package Warning::NotInit;
use feature 'say';
use Moose::Role;

has '_init' => (
  is      => 'rw',
  isa     => 'Bool',
  default => 0
);

before method => sub {
  my $self  = shift;
  my $class = ref($self);
  warn "==> $class is not initialized\n" unless $self->_init;
};

package Foo;
use feature 'say';
use Moose;
with 'Warning::NotInit';

sub init {
  shift->_init(1);
}

sub method {
  say "in Foo::method";
}

package Bar;
use feature 'say';
use Moose;
extends 'Foo';
with 'Warning::NotInit';

sub method {
  say "in Bar::method";
}


package main;
use feature 'say';

my $foo = Foo->new;
say "foo the wrong way:";
$foo->method;

say "foo the right way:";
$foo->init;
$foo->method;

my $bar = Bar->new;
say "bar the wrong way:";
$bar->method;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top