Question

Is there any way of knowing the trigger caller attribute in Moose ?

For example, taking the example from Moose::Manual::Attributes:

has 'size' => (
  is      => 'rw',
  trigger => \&_size_set,
);

sub _size_set {
  my ( $self, $size, $old_size ) = @_;

  my $msg = $self->name;

  if ( @_ > 2 ) {
      $msg .= " - old size was $old_size";
  }

  $msg .= " - size is now $size";
  warn $msg;
}

Is it possible in _set_size to know that the attribute size called it, without needing to specify the name of the caller attribute explicitly?

EDIT: updated per comment.

Était-ce utile?

La solution 2

Here's what @RsrchBoy refers to as the "proper way"...

use v5.14;
use strict;
use warnings;

BEGIN {
    package MooseX::WhatTheTrig::Trait::Attribute
    {
        use Moose::Role;
        use Scope::Guard qw(guard);
        after _process_trigger_option => sub
        {
            my $class = shift;
            my ($name, $opts) = @_;
            return unless exists $opts->{trigger};

            my $orig = delete $opts->{trigger};
            $opts->{trigger} = sub
            {
                my $self = shift;
                my $guard = guard {
                    $self->meta->_set_triggered_attribute(undef);
                };
                $self->meta->_set_triggered_attribute($name);
                $self->$orig(@_);
            };
        }
    }

    package MooseX::WhatTheTrig::Trait::Class
    {
        use Moose::Role;
        has triggered_attribute => (
            is     => 'ro',
            writer => '_set_triggered_attribute',
        );
    }
}


package Example
{
    use Moose -traits => ['MooseX::WhatTheTrig::Trait::Class'];

    has [qw(foo bar)] => (
        traits   => ['MooseX::WhatTheTrig::Trait::Attribute'],
        is       => 'rw',
        trigger  => sub {
            my ($self, $new, $old) = @_;
            $_ //= 'undef' for $old, $new;
            my $attr = $self->meta->triggered_attribute;
            say "Changed $attr for $self from $old to $new!";
        }
    );
}

my $obj = Example->new(foo => 1, bar => 2);
$obj->foo(3);
$obj->bar(4);

You'll notice that the "foo" and "bar" attributes share a trigger, but that the trigger is able to differentiate between the two attributes.

Moose::Exporter has some sugar for making this a little less ugly. I might have a play at turning this into a CPAN module some time.

Autres conseils

It might be simpler to create a wrapper that adds one argument:

sub make_trigger {
    my ($name, $sub) = @_;
    return sub {
        my $self = shift;
        $self->$sub($name, @_);
    };
}

has 'size' => (
  is      => 'rw',
  trigger => make_trigger(size => \&_size_set),
);


sub _size_set {
  my ( $self, $name, $size, $old_size ) = @_;
  ...
}

The proper way to do this would be to employ an attribute trait of some sort; one that passes the name, or (preferably) the metaclass instance of the attribute the trigger belongs to. One could even create a trait that allows the class' metaclass to be asked if we're in an attribute trigger, and if so, which one. (This would be transparent and not break anyone's expectations as to how trigger works.)

The easiest would be to curry your triggers as shown in another example.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top