1 ); sub _trigger_attr { print "trigger! value:". shift->attr ."\n" } pac" /> 1 ); sub _trigger_attr { print "trigger! value:". shift->attr ."\n" } pac" /> 1 ); sub _trigger_attr { print "trigger! value:". shift->attr ."\n" } pac"/>

How to NOT execute trigger while constructing object

292 views Asked by At

I have this code:

package Foo;
use Moo;

has attr => ( is => "rw", trigger => 1 );

sub _trigger_attr 
    { print "trigger! value:". shift->attr ."\n" }


package main;
use Foo;

my $foo = Foo->new( attr => 1 );
$foo->attr( 2 );

It returns:

$ perl test.pl
trigger! value:1
trigger! value:2

This is default, documented behavior of Triggers in Moo.

How can I disable trigger execution if the attribute is set via constructor?

Of course I can do it like this:

package Foo;
use Moo;

has attr        => ( is => "rw", trigger => 1 );
has useTriggers => ( is => "rw", default => 0 );

sub _trigger_attr 
{ 
    my $self = shift;
    print "trigger! value:". $self->attr ."\n" if $self->useTriggers 
}

package main;
use Foo;

my $foo = Foo->new( attr => 1 );
$foo->useTriggers( 1 );
$foo->attr( 2 );

And get:

$ perl testt.pl
trigger! value:2

So it works, but ... it feels wrong ;).

3

There are 3 answers

3
tjwrona On BEST ANSWER

I don't know much about Moo, but in Moose you can implement your own code after the constructor. If you can do something like this in Moo it would give you the desired effect.

sub BUILD {
    my $self = shift;

    # Sets "useTriggers" AFTER the object is already constructed.
    $self->useTriggers(1);
};

This would cause useTriggers to be set just after construction so the trigger would be active after the object is constructed, but not before it is constructed.

So you should be able to write:

my $foo->new(attr => 1);
$foo->attr(2);

And get the same output.

1
ikegami On
package Foo;
use Moo;

has attr => ( accessor => '_attr' );

sub attr { 
    my $self = shift;
    my $rv = $self->_attr(@_);
    print "trigger! value: ", $rv if @_;
    return $rv;
}


package main;
use Foo;

my $foo = Foo->new( attr => 1 );
$foo->attr( 2 );
0
Pawel Pabian bbkr On

Because Perl 5.36 was released with signatures no longer being experimental things got complicated in Moose ecosystem.

For Moo nothing changes, this useTriggers trick or building custom accessors stay as valid answers.

For Moose and Mouse when value was changed through attribute accessor old value was also passed. So trigger called from constructor and trigger called on existing attribute were distinguishable by arity. Common pattern was:

sub _change_attribute {
    my ( $self, $new ) = @_;

    return unless int @_ == 3;    # no old value, we're in constructor

    do something...

With signatures there is new issue - one cannot inspect @_ if signatures are used (this is consider experimental, throws a warning and is generally discouraged). So one may fall into this trap when adapting signature to variable trigger sub arity:

sub _change_attribute ( $self, $new, $old = undef ) {

    return unless defined $old;

    do something...
}

This will work only if attribute itself cannot be Undef. Otherwise trigger will not work as expected on

  • $object->attribute( undef );
  • $object->attribute( 123 ); # ooops !

change.

The hacky solution is to mistype old param on purpose as slurpy array:

sub _change_attribute ( $self, $new, @old ) {

    return unless @old;   # no old value, we're in constructor

    do something...
}

This will fire properly when value is changed through attribute accessor (does not matter if attribute can be Undef) and will fire and skip logic when initialized from constructor.