Search code examples
perlmoose

Can I localize the hash reference in a Moose attribute via the accessor?


I have a Moose object with an attribute that contains a hash reference.

package Foo;

use Moose;

has bar => (
  is => 'ro',
  isa => 'HashRef',
  default => sub { {} },
};

In my code, I want to local the hash reference that is inside $foo->bar. I know I can do:

my $foo = Foo->new;

# ...

my %local_bar = ( asdf => 123 );
local $foo->{bar} = \%local_bar;   # THIS LINE

call_to_something_that_needs_bar($foo);

for (keys %local_bar) {
  ...
}

But I don't want to do that1. Is there syntax to localize that structure without going to the internals?


1) The reason I don't want to do that is that $foo is wrapped in an Object::Destroyer instance, so while $foo->bar resolves to Foo, $foo->{bar} actually ends up in the destroyer instance, and $foo->{object}->{bar} is where $foo->bar goes. The code with the local is in production code, but the $foo object is only an Object::Destroyer instance in a test.


Solution

  • local makes a backup of a variable, then places a directive on the stack that restores that variable when it's popped off the stack. The following does something similar:

    use Object::Destroyer qw( );
    
    sub local_attribute {
       my $obj  = shift;
       my $attr = shift;
       my $val  = shift;  # Optional
    
       my $backup = $obj->$attr();
       my $guard = Object::Destroyer->new(
          sub {
             $obj->$attr( $backup )
          }
       );
    
       $obj->$attr( $val );
    
       return $guard;
    }
    
    my $guard = local_attribute( $foo, "bar", \%local_bar );
    

    The above uses object destruction to provide a generic approach. You could also use case-specific exception handling.