I'm currently delegating the builder method to all of the objects that extend one of my base classes. The problem that I'm facing is I need all of the objects to either read an attribute of itself or be passed in a value.
# In Role:
has 'const_string' => (
isa => 'Str',
is => 'ro',
default => 'test',
);
has 'attr' => (
isa => 'Str',
is => 'ro',
builder => '_builder',
);
requires '_builder';
# In extending object - desired 1
sub _builder {
my ($self) = shift;
# $self contains $self->const_string
}
# In extending object - desired 2
sub _builder {
my ($arg1, $arg2) = #_;
# $args can be passed somehow?
}
Is this currently possible or am I going to have to do it some other way?
You can't pass arguments to attribute build methods. They are called automatically by Moose internals, and passed only one argument -- the object reference itself. The builder must be able to return its value based on what it sees in $self, or anything else in the environment that it has access to.
What sort of arguments would you be wanting to pass to the builder? Can you instead pass these values to the object constructor and store them in other attributes?
# in object #2:
has other_attr_a => (
is => 'ro', isa => 'Str',
);
has other_attr_b => (
is => 'ro', isa => 'Str',
);
sub _builder
{
my $self = shift;
# calculates something based on other_attr_a and other_attr_b
}
# object #2 is constructed as:
my $obj = Class2->new(other_attr_a => 'value', other_attr_b => 'value');
Also note that if you have attributes that are built based off of other attribute values, you should define them as lazy, otherwise the builders/defaults will run immediately on object construction, and in an undefined order. Setting them lazy will delay their definition until they are first needed.
You can do something like this:
has 'attr' => (
isa => 'Str',
is => 'ro',
builder => '_pre_builder',
);
sub pre_builder {
_builder(#_, 'your_arg');
}
Related
I have a Moose class Person
package Person;
use Moose;
has 'first_name' => (
is => 'rw',
isa => 'Str',
);
has 'last_name' => (
is => 'rw',
isa => 'Str',
);
has 'check' => (
is => 'rw',
isa => 'CodeRef',
);
no Moose;
__PACKAGE__->meta->make_immutable;
I am initializing a new Person object in another file like so
use Person;
my $user = Person->new(
first_name => 'Example',
last_name => 'User',
check => sub {
print "yo yo\n";
},
);
print "here\n";
$user->check();
print "here\n";
The two here debug statements are printing but the debug message in the subroutine is not.
I'd like to know the correct way for me to pass a function to the constructor such that I can pass an anonymous sub routine to the object.
$user->check() is equivalent to $user->check. It just returns the value of the check attribute (i.e, the coderef) without doing anything with it -- just like any other accessor would. The fact that this attribute holds a coderef doesn't change that.
If you want to retrieve the coderef, then call it, you need another arrow:
$user->check->()
An alternative is to use the trait Code implemented by Moose::Meta::Attribute::Native::Trait::Code, and then define a handle with a different name.
package Person;
use Moose;
has 'check' => (
is => 'rw',
isa => 'CodeRef',
traits => ['Code'],
handles => {
run_check => 'execute',
},
);
And then call it like this
my $user = Person->new(
first_name => 'Example',
last_name => 'User',
check => sub {
print "yo yo\n";
},
);
print "here\n";
$user->run_check;
print "here\n";
This allows you to separate the accessor for the code-ref from the functionality it fulfills.
###############################################################################
# Attributes
###############################################################################
has 'primary_cluster' => (
is => 'ro',
isa => 'Str',
required => TRUE,
);
has 'secondary_cluster' => (
is => 'ro',
isa => 'Str',
required => FALSE,
default => sub {$_[0]->primary_cluster},
);
has '_connection' => (
is => 'ro',
required => FALSE,
init_arg => undef,
default => sub {
Core::mConnection->new(
primary_cluster => $_[0]->primary_cluster,
secondary_cluster => $_[0]->secondary_cluster,
);
},
);
I'm trying to have a private attribute _connection that uses other attributes to create a mConnection object. The problem I'm running into is that inside the default subroutine of _connection, $_[0]->primary_cluster is always undef. Is there any way to guarantee order to the attribute creation or is there a better way to go about doing this?
I do not want this attribute to be lazy; I need it to be created when the object is constructed.
The object is still being constructed! Delay your attribute's initialization until after it's constructed. the following delays its initialization until it's used:
lazy => 1
You could also use a BUILD method instead of a default.
sub BUILD {
my $self = shift;
$self->_connection(
Core::mConnection->new(
primary_cluster => $self->primary_cluster,
secondary_cluster => $self->secondary_cluster,
)
);
}
Of course, you'll need to make the attribute writable first.
I have WrapperClass object that has an InnerClass object as an attribute. The InnerClass object has a weight attribute. My WrapperClass object also has a weight attribute and I want its default value to be whatever the value of the InnerClass object's weight attribute is.
#!/usr/bin/perl
package InnerClass;
use Moose;
has 'weight' => (
is => 'rw',
);
package WrapperClass;
use Moose;
has 'wrapped' => (
is => 'rw',
lazy => 1,
default => sub {InnerClass->new(weight => 1)},
);
has 'weight' => (
is => 'rw',
default => sub {
my $self = shift;
$self->wrapped->weight()
},
lazy => 1,
);
The code above works, but in reality InnerClass has many attributes which WrapperClass needs to do the same thing for. Ideally I would do something like this when I'm writing WrapperClass:
use Moose;
has 'wrapped' => (
is => 'rw',
);
my #getDefaultsFromWrappers
= qw(weight height mass x y z label); # etc ...
foreach my $attr (#getDefaultsFromWrappers) {
has $attr => (
is => 'rw',
default => sub {
# Somehow tell the default which attribute
# it needs to call from wrapped object?
my $self = shift;
$self->wrapped->???()
},
lazy => 1,
);
}
However, there is no way of passing an argument to a default or builder to tell it which attribute it is building. I've considered using caller but this seems like a hack.
Does anyone know how I could accomplish this style of attribute declaration or is it a case of declaring each attribute and its default separately?
You can use $attr where your question marks are because it is still in scope when you declare the attributes.
foreach my $attr (#getDefaultsFromWrappers) {
has $attr => (
is => 'rw',
default => sub { shift->wrapped->$attr() },
lazy => 1,
);
}
The following is a possible alternative, which you might want to use if your attribute declarations are not uniform:
has weight => (
is => 'rw',
isa => 'Num',
default => _build_default_sub('weight'),
lazy => 1,
);
has label => (
is => 'rw',
isa => 'Str',
default => _build_default_sub('label'),
lazy => 1,
);
sub _build_default_sub {
my ($attr) = #_;
return sub { shift->wrapped->$attr };
}
This may be better handled by method delegation and default values in the inner object.
With these, the example you gave can be better written as:
#!/usr/bin/perl
use strict;
use warnings;
package InnerClass;
use Moose;
has weight => (
is => 'rw',
default => 1,
);
package WrapperClass;
use Moose;
has wrapped => (
is => 'rw',
isa => 'InnerClass',
lazy => 1,
default => sub { InnerClass->new },
handles => [ 'weight' ],
);
package main;
my $foo = WrapperClass->new;
print $foo->weight;
Any additional defaults would be added as default on the InnerClass, and within the WrapperClass, add to wrapped 'handles' array ref to indicate that it should be delegated to that object.
If don't want the defaults to be applied to all instances of InnerClass, then you can remove the default from there, specify all attributes required (to give better error detection), and specify all attributes in the default constructor.
In Perl/Tk, one can define textvariables for widgets. It's a reference to some scalar that holds the value.
Someone showed me how to use Moose attribute coercion to use Moose attributes as textvariable (cool!). This is how it works:
subtype 'TkRef' => as 'ScalarRef';
coerce 'TkRef', from 'Str', via { my $r = $_; return \$r };
has 'some_val' => (is => 'rw', isa => 'TkRef', coerce => 1, default => 'default value');
$mw->Entry(-textvariable => $self->some_val);
$mw->Label(-textvariable => $self->some_val); # will always Show what we type into the entry
However, when I want to set a new value for the attribute, I have to dereference it like this:
${$self->some_val} = 'blarg'; # dereference
Simply setting the attribute won't work, as the reference needs to remain the same over the life of the object (that is, the attribute value itself cannot change).
Is there a way to use the nice Moose attribute coerce feature without losing the possibility to set the attribute with $self->some_val('blarg'); ? Some sort of reverse-coercion?
Make the accessors private, and then provide a wrapper for the accessor. Something like this:
subtype 'TkRef', as 'ScalarRef';
coerce 'TkRef', from 'Str', via { my $r = $_; return \$r };
has _some_val => (
is => 'rw',
isa => 'TkRef',
coerce => 1,
init_arg => 'some_val',
default => 'default value',
);
sub some_val {
my $self = shift;
if (#_ and not ref $_[0]) {
${$self->_some_val} = shift;
}
elsif (#_ and ref $_[0]) {
${$self->_some_val} = ${+shift};
}
$self->_some_val(#_);
}
In my previous question Moose - Loading values from conf files... Jack Maney was kind enough to provide an example of how to do so using Moose.
In order to make the configuration object even more general I decided to use Config::Auto.
The problem is that I still am very green as to how Moose works. For instance, Jack's example is:
package My::Module;
use Moose;
has 'config'=>(isa=>'HashRef[Str]',is=>'rw',required=>1);
around BUILDARGS=>sub
{
my $orig=shift;
my $class=shift;
my $args=shift; #other arguments passed in (if any).
my %config_hash=();
open(my $read,"<","config_file") or confess $!;
while(<$read>)
{
chomp;
my #array=split /:/;
$config_hash{$array[0]}=$array[1];
}
close($read);
$args->{config}=\%config_hash;
return $class->$orig($args);
};
no Moose;
1;
I had modified it to this:
#!/usr/local/bin/perl
package DART::Setup;
use namespace::autoclean;
use Moose;
use Config::Auto;
our $VERSION = '0.0.1';
has 'EMPTY' => ( isa => 'Str', is => 'ro', default => q{} );
has 'PPLTESTEXECUTIONID' => ( isa => 'Int', is => 'ro', default => 0 );
has 'SARQTESTEXECUTIONID' => ( isa => 'Int', is => 'ro', default => 0 );
has 'ISPROXY' => ( isa => 'Int', is => 'ro', default => 0 );
has 'LOCALHOST' => ( isa => 'Str', is => 'ro', default => '127.0.0.1' );
has 'config'=>(isa=>'HashRef[Str]',is=>'rw',required=>1);
has 'SSO' => ( isa => 'Str', is => 'rw', default => q{} );
has 'cookieFile' => ( isa => 'Str', is => 'rw', default => q{} );
around BUILDARGS=>sub
{
my $orig=shift;
my $class=shift;
my $args=shift;
my $cfg = Config::Auto::parse($args);
my %config_hash = %{$cfg};
$args->{config}=\%config_hash;
return $class->$orig($args);
};
return 1;
But to be honest I'm not sure what I'm doing here. First off, how many arguments do I need to provide when I'm creating a new Setup object? Do I just pass it the path to my configuration file, something like:
my $newConfig = DART::Setup->new('/home/y/conf/MyApp/MyApp.cfg');
Or do I need to provide arguments for $orig and $class?
Finally, how do I now access my newly loaded configurations? Can I do something like:
my %configHash = %{$newConfig->config()};
foreach my $key (keys %configHash) {
print "the key is, $key, and the value is: $configHash{$key}\n";
}
Am I understanding this correctly?
Okay, inside of BUILDARGS, you want to read in the config file and pass the key-value pairs into the config attribute. Here's a modified version with another attribute for the config file.
package My::Module;
use Moose;
use Config::Auto;
has 'config'=>(isa=>'HashRef[Str]',is=>'rw',required=>1);
has 'config_file'=>(isa=>'Str',is=>'ro');
around BUILDARGS=>sub
{
my $orig=shift;
my $class=shift;
my $args=shift; #currently {config_file=>'/path/to/file/config_file.conf'} (or whatever)
#make sure we've been passed a config file
confess "No config file found in BUILDARGS" unless defined $args->{config_file};
#Now, we open the user-specified config file via Config::Any
my $ca=Config::Auto->new(source=>$args->{config_file},format=>"colon");
my $parsed=$ca->parse; #hash reference containing the parsed data.
#Now, we add this to our arguments that will become our attributes:
$args->{config}=$parsed;
return $class->$orig($args);
}
no Moose;
1;
The main thing to realize about BUILDARGS is that it takes the following arguments: the names of the class and original constructor (which are passed to Moose::Object) and then any other arguments passed to the constructor. So, if you call
my $mm=My::Module->new({config_file=>"/path/to/file/file.conf"});
Then, in BUILDARGS, we initially have
$args=={config_file=>"/path/to/file/file.conf"}
But after parsing the file and adding the $parsed hash reference, it turns into
$args=={config_file=>"/path/to/file/file.conf",config=>{server=>"mozilla.org",protocol=>"HTTP",...}}
etc, etc.
By writing my $cfg = Config::Auto::parse($args); inside of BUILDARGS, you're trying to pass a config_file argument to the parser in Config::Auto, and it'll have no idea what to do with it.
BUILDARGS is just a way to hook into the constructor at the beginning of construction. For your reference, the construction section of the manual might help in your understanding on that.
Jack Maney's answer is perfectly fine. Building on his suggestion to use a config_file attribute, here's an alternative that uses a lazy builder. Personally, I prefer these to BUILDARGS because the code is slightly simpler. Builders are used to set the default value of an attribute. You need to make it lazy because building the attribute depends on another attribute (in this case config_file) to ensure that construction of the object has completed and the attribute has been set.
package DART::Setup;
use namespace::autoclean;
use Moose;
use MooseX::FileAttribute;
use Config::Auto;
# use MooseX::FileAttribute (optional) to enforce that the file actually exists
# - just a shortcut to remove some boilerplate code if you want
has_file 'config_file' => (
is => 'ro',
must_exist => 1,
required => 1,
);
has 'config' => (
isa => 'HashRef[Str]',
is => 'ro',
# disallow this attribute to be set by the constructor
init_arg => undef,
# cause this attribute to be set up after construction
lazy => 1,
builder => '_build_config',
# or alternatively, use 'default' instead of 'builder'
# (but it still needs to be lazy)
#default => sub { Config::Auto::parse( shift->config_file ) },
);
sub _build_config {
my ( $self ) = #_;
my $config = Config::Auto::parse( $self->config_file );
return $config;
}