Search code examples
perlprivatemoose

Using Moose's before to alter method arguments clashes with Privacy


As mentioned earlier today, I am trying to set attributes to an instance when one of its methods is called. I also want to make this attribute private. As has been pointed out to me, I cannot set that attribute to ro as this also prohibits read-access from within the class. Therefore I now set it to rw but I've started using the MooseX::Privacy module. My attribute declaration thus looks like this:

has 'grow_params' => (
  is  => 'rw',
  isa => 'HashRef',
  traits => [qw/Private/],
);

This works fine if I simply do something like this:

sub grow {
  my ($self, $params) = @_;
  $self->grow_params($params);
}

However, I want to do some parameter checks. From reading the documentation, it seems that the best place to do this is before. When I try this, though, I run into problems.

For instance, with before:

before 'grow_params' => sub {
  my ($self, $params) = @_;
  # Setting default value
  $params->{'overripe'} = exists $params->{'overripe'} ? $params->{'overripe'} : 0;
  # Making sure its boolean
  confess "Argument 'overripe' has to be 0 or 1"
    unless ($params->{'overripe'} == 0 || $params->{'overripe'} == 1);
};

This leads to the following error:

Attribute grow_params is private at C:/strawberry/perl/site/lib/MooseX/Privacy/Meta/Attribute/Private.pm line 13.

(I also tried setting the attribute to Protected, guessing that perhaps a subclass was created, but to no avail.) The attribute is indeed private, but I am trying to modify it from within the class though, right? The trace looks like this, so I'd assume that the actual setting is being tried by Banana, or am I missing something?

Attribute grow_params is private at c:/strawberry/perl/site/lib/MooseX/Privacy/Meta/Attribute/Private.pm line 13.
        MooseX::Privacy::Meta::Attribute::Private::_check_private(Moose::Meta::Class::__ANON__::SERIAL::9=HASH(0x3fd4a00), "Class::MOP::Method::Wrapped", "grow_params", "Banana", "Banana") called at c:/strawberry/perl/site/lib/MooseX/Privacy/Meta/Attribute/Privacy.pm line 31
        Banana::grow_params(Banana=HASH(0x25223e8), HASH(0xed97a8)) called at c:/strawberry/perl/site/lib/Class/MOP/Method/Wrapped.pm line 44
        Banana::_wrapped_grow_params(Banana=HASH(0x25223e8), HASH(0xed97a8)) called at c:/strawberry/perl/site/lib/Class/MOP/Method/Wrapped.pm line 95
        Banana::grow_params(Banana=HASH(0x25223e8), HASH(0xed97a8)) called at C:\xampp\htdocs\grinding\banana\/Banana.pm line 31
        Banana::grow(Banana=HASH(0x25223e8), HASH(0xed97a8)) called at run.pl line 16

The full code to run yourselves.

# Banana.pm
package Banana;

use strict;
use warnings;
use Carp qw( confess );

use Moose;
use MooseX::Privacy;

has ['peel', 'edible'] => (
  is  => 'ro',
  isa => 'Bool',
  required => 1,
);

has 'color' => (
  is  => 'ro',
  isa => 'Str',
  required => 1,
);

has 'grow_params' => (
  is  => 'rw',
  isa => 'HashRef',
  traits => [qw/Private/],
);

sub grow {
  my ($self, $params) = @_;
  $self->grow_params($params);
}

before 'grow_params' => sub {
  my ($self, $params) = @_;
  # Setting default value
  $params->{'overripe'} = exists $params->{'overripe'} ? $params->{'overripe'} : 0;
  # Making sure its boolean
  confess "Argument 'overripe' has to be 0 or 1"
    unless ($params->{'overripe'} == 0 || $params->{'overripe'} == 1);
};

1;

# run.pl
use strict;
use warnings;

use File::Basename qw(fileparse dirname);
use Cwd qw(abs_path);

# Add current directory to @INC
use lib (fileparse(abs_path($0)))[1];

use Banana;

my $chiquita = Banana->new({
  peel => 1,
  edible => 0,
  color => 'green'
});

$chiquita->grow({
  size => 'medium',
  color => 'yellow'
});

Solution

  • If we look into the implementation of MooseX::Privacy, more specifically into MooseX::Privacy::Meta::Attribute::Private v0.05, we can see what's happening.

    sub _check_private {
        my ($meta, $caller, $attr_name, $package_name) = @_;
        confess "Attribute " . $attr_name . " is private"
            unless $caller eq $package_name;
    }
    

    So, the problem must be that $caller is not equal to $package_name. Which is a way to express the notion of privacy in Perl terms, I guess.

    Who's the caller of a before method? I slightly changed the code to print $caller before the check. Here's the culprit:

    Class::MOP::Method::Wrapped

    which has to do with how method modifiers are implemented (as the name suggests).

    The way I see it, your expectations are reasonable and this is a defect in MooseX::Privacy and the way it deals with the inner workings of MOP's ecosystem.