How to call method within builder - perl

I have a class with an attribute set up as follows:
has _data => (
is => 'ro',
lazy => 1,
builder => '_load',
);
sub _load {
my $self = shift;
return retrieve $self->_file;
}
However I now want to call a method already defined on the class before returning the data.
In old-school Perl OO, I'd be doing something like this:
sub _load {
# Assuming laziness is implemented somewhere else.
my $self = shift;
$self->{_data} = retrieve $self->_file;
$self->refresh; # which does something with $self->{_data}
return $self->{_data};
}
But I can't figure out a 'clean' way to do this in Moose.
I've considered the following, but think they are quite ugly, and that there must be a better way of doing this.
If I make _data read-write, I could potentially write the data to the accessor, call the method then return the value from the accessor for Moose to write back to the accessor.
If I turn it into a plain old method then I'd have to define another attribute, say _raw_data, store the data in there, modify refresh() to use that attribute, and everything else uses _data().
Violate encapsulation and access the underlying $self->{_data} directly.
I tried an after '_load' => \&refresh;, but that just created an endless loop.

This would be a nice use of triggers:
has _data => (
is => 'ro',
lazy => 1,
builder => '_load',
trigger => sub { shift->refresh },
);
Except that triggers don't work on default/built values - only on values passed to the constructor explicitly, or passed to a writer/accessor method. Sad face. :-(
One solution would be to rewrite your refresh method so that instead of operating on $self->_data, it can accept a parameter (perhaps falling back to operating on $self->_data if no parameter is given.
sub _load {
my $self = shift;
my $tmp = retrieve $self->_file;
$self->refresh($tmp);
return $tmp;
}
sub refresh {
my $self = shift;
my $data = scalar(#_) ? $_[0] : $self->_data;
# do something with $data
}

Related

Design a perl Moose class to facilitate testing

I am trying to design a class in perl. I am using Mooose. I am using an outside module (let's name it PITA::Parser.
has _parser => (
is => 'ro',
isa => 'object',
builder => _create_parser_object);
#other members here
sub _create_parser_object {
#simplified code
return PITA::Parser->new();
}
sub BUILD {
my $self = shift;
$self->_values($self->load_and_validate_data());
}
sub _load_and_validate_data {
my $values_href;
foreach $key (#key_names) {
$values_href->{$key} = $self->_parser->get_value();
#code to validate the values
return $values_href;
}
I want to mock out the PITA::Parser object. This object looks for a specific file (during new) that is not in my test folder, but rather in the environment where my code will be deployed. So, I am trying to mock it out as such:
my $mock_parser = Test::MockObject->new();
$mock_parser->mock('new', sub {});
$mock_parser->mock('get_value', sub {});
Then I want to create an object of my class
my $my_class_object(_parser => $mock_parser);
However, this does not work, I get an error that get_value can not be located by Test::MockObject.
You can use Test::MockObject to mock the parser object and pass it when creating your own object.
my $mock = Test::MockObject->new();
$mock->mock( 'frobnicate',
sub { return 'file that is not part of test environment' } );
my $obj = Your::Class->new(parser => $mock);
ok( $obj->load_and_validate_data );
It will create an object that has a method frobnicate. When called in your load_and_validate_data, it will return the controlled values you want it to return. There's a bunch of other stuff you can do with it. I suggest you take a look at the documentation.

Moose's attribute vs simple sub?

How to decide - what is the recommended way for the next code fragment?
I have a Moose-based module, where some data is a simple HashRef.
It is possible to write - as a Mooseish HashRef, like:
package Some;
has 'data' => (
isa => 'HashRef',
builder => '_build_href',
init_arg => undef,
lazy => 1,
);
sub _build-href {
my $href;
$href = { a=>'a', b=>'b'}; #some code what builds a href
return $href;
}
vs
sub data {
my $href;
$href = { a=>'a', b=>'b'}; #some code what builds a href
return $href;
}
What is the difference? I'm asking because when calling:
my $obj = Some->new;
my $href = $obj->data;
In both case I get a correct HashRef. So when is it recommended to use a Moose-ish has construction (which is longer) vs a simple data sub?
PS: probably this question is so simple for an average perl programmer, but please, keep in mind, I'm still only learning perl.
If you have an attribute, then whoever is constructing the object can set the hashref in the constructor:
my $obj = Some->new(data => { a => 'c', b => 'd' });
(Though in your example, you've used init_arg => undef which would disable that ability.)
Also, in the case of the attribute, the builder is only run once per object while with a standard method, the method might be called multiple times. If building the hashref is "expensive", that may be an important concern.
Another difference you'll notice is with this:
use Data::Dumper;
my $obj = Some->new;
$obj->data->{c} = 123;
print Dumper( $obj->data );

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

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?
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/
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.
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/],
);
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.
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;

How can I require a Moose constructor arg that is not a attribute?

I have a Moose object module that should accept a relatively large data structure (ds) as one of its constructor arguments. It is used to calculate some of the object's attributes. However, I do not wish to store ds itself as an attributes -- it is only needed during the construction of the object.
I thought of using BUILDARGS but then I'm not sure how to define that ds is a required argument.
How can I work around this?
I'd be inclined to have a constructor that takes only the calculated values derived from your data structure. Then use a different method to take limited params plus the data structure as args.
sub generate_foo_from_ds {
my $class = shift;
my %arg = #_;
my $ds = $arg{foo_data};
# Get attributes from args
my %attrib;
for (qw(foo bar baz ) {
croak "Attrib '$_' is required" unless exists $arg{$_};
$attrib{$_} = $arg{$_};
}
# calculate some more attributes here.
$attrib{griz} = $ds->{whee} * $ds->{whoosh} / $ds->{whiz}[12];
my $foo = $class->new( %attrib );
return $foo;
}
Then make your objects like so:
my $foo = Foo->generate_foo_from_ds( foo_data => $bar, foo => 1, bar => 2, baz => 2 );
Now you don't have to worry about weird serialization issues or BUILDARGS or even BUILD. You have a simple method and that is all.
You could use either BUILD or BUILDARGS for this. It's hard to say which would be better without knowing more about what you're trying to do, but I'd guess BUILD would be the better choice.
sub BUILD {
my $self = shift;
my $args = shift;
my $ds = $args->{ds} or confess "Argument (ds) is required";
$self->some_attr($ds->{...});
$self->other_attr($ds->{foo}[3]);
...
} # end BUILD
If you want Moose to check the type and ensure it's present, you have to make it an attribute. But you can clear it in the BUILD method after you use it.
has 'ds' => (
is => 'ro',
isa => 'SomeType',
required => 1,
clearer => '_clear_ds',
);
sub BUILD {
my $self = shift;
my $args = shift;
my $ds = $self->ds;
$self->_clear_ds;
$self->some_attr($ds->{...});
$self->other_attr($ds->{foo}[3]);
...
} # end BUILD
You could name the reader method something else (like _ds) if you wanted to.

How do I override autogenerated accessors in Perl's Class::DBI?

I followed the example at http://wiki.class-dbi.com/wiki/Overriding_autogenerated_accessors
I want to modify the URL before it is inserted to the database:
package Hosting::Company;
use base 'Class::DBI';
my $class = __PACKAGE__;
$class->table('Companies');
$class->columns(Primary => 'CompanyId');
$class->columns(Others => qw/Name Url Comment/);
sub Url {
my $self = shift;
# modify URL.
if (#_) {
$_[0] = 'aaaaaaaaaaaa';
# return $self->_Url_accessor('aaaaaaaaaaaa'); - doesn't work either
}
# Back to normal Class::DBI
return $self->_Url_accessor(#_);
}
But it doesn't work:
my $company = Hosting::Company->insert({ Name => 'Test', Url => 'http://http://url' });
print $company->Url, "\n";
Shows:
http://http://url
I wish the Class:DBI mailing list were still alive!
In you URL accessor, you check whether a parameter was passed to that method. But you aren't passing anyhting in so the accessor will do nothing but call _Url_accessor(). You should probably call _Url_accessor first and then modify the result:
sub Url {
my $self = shift;
# Was there a param passed in?
if ( #_ ) {
# Do you really want to modify it here?
return $self->_Url_accessor(#_);
}
else {
my $url = $self->_Url_accessor();
# mangle result here:
$url = 'aaaaaaaaa';
return $url;
}
}
If you want to change the URL before it even goes in the database, I guess you must provide a normalize_column_values in your class and this will be called each time an insert is made.
Overriding an accessor does not change insert. The best way to handle data normalization is to override normalize_column_values(). But Manni is right, your accessor is busted.
PS The CDBI mailing list is still active, just hasn't seen much posting. Most have moved on to DBIx::Class.