4

In my real code I want to "synchronize" a Moo (or Moose if Moo won't work) object with a hash (in reality a tied hash), so that reading a property of the Moo object would read the corresponding value from the hash and writing a property of the Moo object would store into the hash.

The following is a simplified code:

#!/usr/bin/perl

use feature qw(say);

package X;
use Moo;
use Data::Dumper;

my $BusinessClass = 'X';

has 'base' => (is => 'rw', builder => 'base_builder');

sub base_builder {
  return {};
}

foreach my $Key (qw(a b c)) {
  {
    no strict 'refs';
    *{"${BusinessClass}::$Key"} = sub {
      if (@_ == 2) {
        return $_[0]->base->{$Key} = $_[1];
      } else {
        return $_[0]->base->{$Key};
      }
    };
    has $Key => ( is        => 'rw',
                  lazy      => 0,
                  required  => 0,
                  reader => "${BusinessClass}::_access1_$Key",
                  writer => "${BusinessClass}::_access2_$Key",
                );
  }
}

my $obj = X->new(a=>123, b=>456);
print Dumper $obj->base;
$obj->c(789);
print Dumper $obj->base;

The trouble is that attributes passed to new function are not stored in the has $obj->base (but they should be). In the above code example the attribute c is properly stored as it should, but a and b are not stored into the hash. This is a bug.

What are good ways to handle this situation?

porton
  • 5,214
  • 11
  • 47
  • 95

1 Answers1

3

This can be solved by adding:

sub BUILD {
  my ($self, $args) = @_;

  foreach my $Key (keys %$args) {
    $self->base->{$Key} = $args->{$Key};
    my $clearer = "_clear_local_$Key";
    $self->$clearer();
  }
}

Complete code:

#!/usr/bin/perl

use feature qw(say);

package X;
use Moo;
use Data::Dumper;

my $BusinessClass = 'X';

has 'base' => (is => 'rw', builder => 'base_builder');

sub base_builder {
  return {};
}

sub BUILD {
  my ($self, $args) = @_;

  foreach my $Key (keys %$args) {
    $self->base->{$Key} = $args->{$Key};
    my $clearer = "_clear_local_$Key";
    $self->$clearer();
  }
}

foreach my $Key (qw(a b c)) {
  {
    no strict 'refs';
    *{"${BusinessClass}::$Key"} = sub {
      if (@_ == 2) {
        return $_[0]->base->{$Key} = $_[1];
      } else {
        return $_[0]->base->{$Key};
      }
    };
    has $Key => ( is        => 'rw',
                  lazy      => 0,
                  required  => 0,
                  reader => "${BusinessClass}::_access1_$Key",
                  writer => "${BusinessClass}::_access2_$Key",
                  clearer => "_clear_local_$Key",
                );
  }
}

my $obj = X->new(a=>123, b=>456);
print Dumper $obj->base;
$obj->c(789);
print Dumper $obj->base;

print Dumper {%$obj};
porton
  • 5,214
  • 11
  • 47
  • 95
  • Well, however, my solution is not completely right, as revealed by `print Dumper {%$obj};` added at the end of the code. It stores `a` and `b` **both** into the object `$obj` and into the hash `$obj->base`. This is a bug – porton Sep 12 '16 at 10:57
  • I've edited the code (added clearer methods). Now it works as it should – porton Sep 12 '16 at 11:54
  • There is yet one error: `base_builder` is called two times – porton Sep 12 '16 at 13:11
  • This error however has an easy fix: replace `has 'base' => (is => 'rw', builder => 'base_builder')` with `has 'base' => (is => 'rw')` – porton Sep 12 '16 at 13:12
  • I've edited the code again. Now `has 'base' => (is => 'rw', builder => 'base_builder')` but it works as expected – porton Sep 12 '16 at 16:06