How can I create internal (private) Moose object variables (attributes)?
Asked Answered
N

5

19

I would like some attributes (perhaps this is the wrong term in this context) to be private, that is, only internal for the object use - can't be read or written from the outside.

For example, think of some internal variable that counts the number of times any of a set of methods was called.

Where and how should I define such a variable?

Nevermore answered 22/10, 2010 at 11:52 Comment(0)
I
15

The Moose::Manual::Attributes shows the following way to create private attributes:

has '_genetic_code' => (
   is       => 'ro',
   lazy     => 1,
   builder  => '_build_genetic_code',
   init_arg => undef,
);

Setting init_arg means this attribute cannot be set at the constructor. Make it a rw or add writer if you need to update it.

/I3az/

Improvement answered 22/10, 2010 at 12:17 Comment(2)
The question asks about reading as well as writing. As far as I can tell, in the Jan. 2012 version of Moose (2.0401), attributes made in this way are still readable from outside the object itself. For example, my $m = MooseObject->new(); print $m->_genetic_code; would print whatever is stored in the attribute. I don't know if there is a way around that. It's never bothered me, but I figure it's worth mentioning.Ingeringersoll
@AlanW.Smith Would changing is => 'ro' to is => 'bare' help? In that case, the only way to access _genetic_code from outside would be to peek under Moose itself and look at the object hash or something, since Moose wouldn't create the read accessor also. Is that correct?Lionel
L
9

You can try something like this:

has 'call_counter' => (
    is     => 'ro',
    writer => '_set_call_counter',
);

is => 'ro' makes the attribute read only. Moose generates a getter. Your methods will use the getter for incrementing the value, like so:

sub called {
    my $self = shift;
    $self->_set_call_counter( $self->call_counter + 1 );
    ...
}

writer => '_set_call_counter' generates a setter named _set_call_counter. Moose does not support true private attributes. Outside code can, technically, call _set_call_counter. By convention, though, applications do not call methods beginning with an underscore.

Loudhailer answered 22/10, 2010 at 12:12 Comment(2)
Privacy is about data hiding, not data security. Perl doesn't support true private attributes because you can access the attributes directly. I just wanted to point out that if you could only access them indirectly that would be another story...Peachy
Moose doesn't natively support private attributes. Perl can quite happily support them using for example a less-than-natural closure based instance type. The hoops you have to jump through to make them work aren't worth the effort for most use cases. Excuse the pedantry.Coachwhip
I
7

I think you want MooseX::Privacy.

The perldoc tells you all you should need - it adds a new trait to your attributes allowing you to declare them as private or protected:

has config => (
    is     => 'rw',
    isa    => 'Some::Config',
    traits => [qw/Private/],
);
Idyll answered 28/1, 2012 at 12:1 Comment(0)
I
4

I haven't been able to figure out a way to make Moose attributes completely private. Whenever I use has 'name' => (...); to create an attribute, it is always exposed to reading at a minimum. For items I want to be truly private, I'm using standard "my" variables inside the Moose package. For a quick example, take the following module "CountingObject.pm".

package CountingObject;

use Moose;

my $cntr = 0;

sub add_one { $cntr++; }

sub get_count { return $cntr; }

1;

Scripts that use that module have no direct access to the $cntr variable. They must use the "add_one" and "get_count" methods which act as an interface to the outside world. For example:

#!/usr/bin/perl 

### Call and create
use CountingObject;
my $co = CountingObject->new();

### This works: prints 0
printf( "%s\n", $co->get_count() );

### This works to update $cntr through the method
for (1..10) { $co->add_one(); }

### This works: prints 10
printf( "%s\n", $co->get_count() );

### Direct access won't work. These would fail:
# say $cntr;
# say $co->cntr;

I'm new to Moose, but as far as I can tell, this approach provides completely private variables.

Ingeringersoll answered 28/1, 2012 at 6:2 Comment(1)
Beware though that "my" variables are shared between all your object instances because they're part of the package, and not part of the blessed hash that represents the object. This is usually not what you want.Lyric
S
3

Alan W. Smith provided a private class variable with a lexical variable, but it is shared by all objects in the class. Try adding a new object to the end of the example script:

my $c1 = CountingObject->new();
printf( "%s\n", $c1->get_count() );
#  also shows a count of 10, same as $co

Using MooseX:Privacy is a good answer, though if you can't, you can borrow a trick from the inside-out object camp:

package CountingObject;

use Moose;

my %cntr;

sub BUILD { my $self = shift; $cntr{$self} = 0 }

sub add_one { my $self = shift; $cntr{$self}++; }

sub get_count { my $self = shift; return $cntr{$self}; }

1;

With that, each object's counter is stored as an entry in a lexical hash. The above can be implemented a little more tersely thus:

package CountingObject;

use Moose;

my %cntr;

sub add_one { $cntr{$_[0]}++ }

sub get_count { return $cntr{$_[0]}||0 }

1;
Saturate answered 14/3, 2012 at 14:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.