How to store Hash of Hashes in Moose? - perl

i was wondering, what is the best way to store Hash of Hashes in Moose. Lets take for example a Hash like this:
my %hash = ('step1' => {'extraction' => \$object1,
'analysis' => \$object2},
'step2' => {'extraction' => \$object3,
'analysis' => \$object4});
but i want to save this one in a moose attribute. How should i organize the access (reading, writing) on this. Examples on the net are mostly for "flat" hashes. But then you can use helpers like Moose::Meta::Attribute::Native::Trait::Hash. Is there something similar for hash of hashes?
Reason for this is, that i want to iterate over the step-keys and access the object-instances in that. Or is there a better, more Moose-like way to do this?
Thanks in advance!!!

You can store a hash of hashes in a Moose object in pretty much the same way as you would store any other hash:
has steps => ( is => 'ro', isa => 'HashRef' );
You can, however, be more specific to declare it as the specific kind of hash you need to store as a way to verify that anything stored in that slot is the right kind of thing:
has steps => ( is => 'ro', isa => 'HashRef[HashRef[Object]]' );
Depending on the data, I might also change Object here to the class name. You can get even fancier and use MooseX::Types and MooseX::Types::Structured to specify an even more exacting structure.
As for helpers to to step over your structure, I don't know of anything in Moose or MooseX to do that. If you know the structure of your data, it's probably best to just implement a subroutine to do what you need yourself. Your code will likely perform better and do what you need better than any generic traversal.
Edit/Additional Info: Each Moose attribute creates an accessor method no your class which returns the stored value, so accessing the data is:
# Assuming we put the attribute in a package named StepTool
my $step_tool = StepTool->new(
steps => { 'step1' => {'extraction' => \$object1,
'analysis' => \$object2},
'step2' => {'extraction' => \$object3,
'analysis' => \$object4} },
);
# To do something one of the values
do_something($step_tool->steps->{step1}{extraction});
# To iterate over the structure, could be done in a method on StepTool
for my $step_name (keys %{ $step_tool->steps }) {
my $step = $step_tool->steps->{ $step_name };
for my $action_name (keys %$step) {
my $object = $step->{ $action_name };
do_something($object);
}
}
# If doing the above as a method, $self is the Moose object, so...
sub traverse_steps {
my ($self) = #_;
for my $step_name (keys %{ $self->steps }) {
... # just like above
}
}
And one other note, you could still use traits => [ 'Hash' ] and add some handles to give yourself some additional helpers, if you want.
If the data structure is more free form than that, you might want to look into something like Data::Visitor to iterate over your structure in your subroutine. (I have had some difficult to debug, weird problems with Data::Visitor, so I try to avoid it when I can.)

There is also a type-safe approach inspired by Moose: How to get an array of objects? Traits?
There is a class to hold the outer hash (StepTool::Steps) that has traits => ['Hash']. This approach can be nested infinitely deep using e.g. Arrays and Hashes:
package StepTool;
use Moose;
has 'steps' => (
'is' => 'rw',
'isa' => 'StepTool::Steps',
'default' => sub { StepTool::Steps->new() },
);
package StepTool::Steps;
use Mouse;
has '_steps' => (
is => 'ro',
isa => 'HashRef[StepTool::Step]',
traits => ['Hash'],
default => sub { {} },
handles => {
# You'll probably want a fuller set here...
get => 'get',
set => 'set',
keys => 'keys',
}
);
package StepTool::Step;
use Mouse;
has 'extraction' => (
is => 'rw',
);
has 'analysis' => (
is => 'rw',
);
package main;
my $object1 = bless {}, 'Foobar1';
my $object2 = bless {}, 'Foobar2';
my $object3 = bless {}, 'Foobar3';
my $object4 = bless {}, 'Foobar4';
my $stepTool = StepTool->new();
# set up step1 one field at a time.
$stepTool->steps->set('step1', StepTool::Step->new());
# I have no idea why the OP wants references to objects
# everywhere but he does...
$stepTool->steps->get('step1')->extraction(\$object1);
$stepTool->steps->get('step1')->analysis(\$object2);
# set up step2 all at once
$stepTool->steps->set('step2', StepTool::Step->new(
extraction => \$object3,
analysis => \$object4
));
# or, less elegantly, initialize an entire StepTool:
my $stepTool2 = StepTool->new(
steps => StepTool::Steps->new(
_steps => {
step1 => StepTool::Step->new(
extraction => \$object1,
analysis => \$object2
),
step2 => StepTool::Step->new(
extraction => \$object3,
analysis => \$object4
),
}
),
);
printf "step1->analysis is a ref to an instance of class: %s\n",
ref(${$stepTool->steps->get('step1')->analysis});

Related

Perl Moose - What are the arguments when Loading valued from configuration files?

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;
}

How can I implement a new handle for a Moose type trait?

Lets's say I wanted to add say functionality to String ( note: this is a more simple example than reality ). So I could have
has foo => (
isa => 'Str',
traits => [ 'String' ],
handles => {
say_foo => 'say',
}
);
which I then of course would be able to use to do.
$self->foo( 'bar' );
$self->say_foo;
which would print literally
'bar\n'
I imagine the subroutine would be something like this
sub _say_attr {
my ( $self, $attr ) = #_;
say $attr;
}
Can anyone help me fill in the gaps on how I might actually implement this? I don't really see much in the way of documentation on how to write your own handles.
I don't really need to know how to modify the String traits. So much as I want to be able to have a generic handler, where I don't need to know the name of the current attribute in order to make it work.
has foo => (
isa => 'Str',
traits => [ 'PrintString' ],
handles => {
say_foo => 'say',
}
);
has bar => (
isa => 'Str',
traits => [ 'PrintString' ],
handles => {
say_bar => 'say',
}
);
so say here is probably an identifier for a function which does not need a hardcoded name of an attribute which is calling it.
Do you really want to add say to String, or would you be content with adding say_foo to foo?
The latter is easy:
has foo => (
isa => 'Str',
traits => [ 'String' ],
handles => {
say_foo => sub { say $_[0]->foo; },
}
);
If you wanted to a more general solution, You should look at Moose::Meta::Attribute::Native::Trait::String and copy/wrap/subclass it rather than trying to change it.

Bread::Board - Injecting parameters with ArrayRef type constraints?

Using Moose and Bread::Board, is it possible to create an object with an attribute that has an ArrayRef[SomeObject] type constraint and have that parameter injected in such a way that:
The ArrayRef constraint is maintained,
Each object that is a member of that ArrayRef has all of its dependencies met by Bread::Board, and
Each object that is a member of that ArrayRef is an object that was created by Bread::Board?
In order to make sure that I'm explaining myself clearly, let's consider an incredibly naive example. Let's say we have a Wheel class:
package Wheel;
use Moose;
has ['size', 'maker'] => (isa => 'Str', is => 'rw', required => 1);
And let's create a Vehicle class where each instance contains a bunch of wheels:
package Vehicle;
use Moose;
has 'wheels' => (
is => 'rw',
isa => 'ArrayRef[Wheel]',
required => 1,
);
Is it then possible to create one or more instances of Wheel and then inject an array reference containing those instances into our new Vehicle instance? This obviously won't work:
my $c = container 'app' => as {
container 'wheels' => as {
service 'maker' => "Bob's Tires";
service 'size' => "195R65/15";
service 'carTires' => (
class => 'Wheel',
dependencies => [ depends_on('maker'), depends_on('size') ],
)
};
container 'vehicles' => as {
service 'sedan' => (
class => 'Vehicle',
dependencies => {
# WON'T WORK
wheels => depends_on('/wheels/carTires'),
}
)
};
};
my $v = $c->resolve(service => 'vehicles/sedan');
Any ideas? Yes, I am aware that it my Vehicles can conceivably have no wheels and that I'm trying to create a one-wheeled sedan, but I think you get my point. :-) This is intended only to be an incredibly trivial example.
You could get the carTires service to return a ArrayRef of Wheels:
container 'wheels' => as {
service 'carTires' => (
block => sub {
return [ map {Wheel->new} 1..4 ];
},
)
};
The Vehicle constructor will take care of the rest in terms of the type constraint, since you've already defined it as ArrayRef[Wheel].
So it will die if you did this instead:
container 'wheels' => as {
service 'carTires' => (
block => sub {
return [ map {Window->new} 1..4 ];
},
)
};

Is it possible to retrieve existing moose objects, rather than create a new one, when the same required attributes are provided?

Suppose i have the following Moose package:
package GSM::Cell;
use Moose;
has 'ID' => (is => 'ro', required => 1);
has [qw(BCCH NEIGHBOUR)] => (is => 'rw', default => undef);
no Moose;
__PACKAGE__->meta->make_immutable;
1;
I then create two objects and add the one as the 'NEIGHBOUR' attribute of the other:
my $a = GSM::Cell->new(ID => 20021, BCCH => 1);
my $b = GSM::Cell->new(ID => 20022, BCCH => 2);
$a->NEIGHBOUR($b);
Somewhere else, e.g. in another procedure, the BCCH attribute of $b could be updated to another value:
$b->BCCH(3);
Now, if i refer to
$a->NEIGHBOUR->BCCH
then i will still get back the initial value of the BCCH attribute instead of the updated value.
I guess the sensible thing to do is to add a reference to $b instead of $b itself which would solve the problem:
$a->NEIGHBOUR(\$b);
However, i have the scenario in a web application where an object equivalent to $b (same ID) is instantiated in a multitude of methods and changes could be done in any one, making it difficult to pass around references of all your created objects.
Ideally, when a call to
my $somevar = GSM::Cell->new(ID => 20022);
is made, an object should only be created if one with the same ID does not already exist.
Is a dictionary the way to go, something like this:
$id = 20022;
my $somevar = $already_created{$id} || GSM::Cell->new(ID => $id);
or are there neater solutions?
It sounds like something MooseX::NaturalKey was designed for.
package GSM::Cell;
use MooseX::NaturalKey;
has 'ID' => (is => 'ro', required => 1);
has [qw(BCCH NEIGHBOUR)] => (is => 'rw', default => undef);
primary_key => ('ID');
no Moose;
__PACKAGE__->meta->make_immutable;
1;
Then later:
my $a = GSM::Cell->new(ID => 20021, BCCH => 1);
my $b = GSM::Cell->new(ID => 20022, BCCH => 2);
$a->NEIGHBOUR($b);
$b->BCCH(3);
say $a->NEIGHBOR->BCCH; # prints 3
my $c = GSM::Cell->new(ID => 20022);
$c->BCCH(4);
say $a->NEIGHBOR->BCCH; # prints 4
Isn't the neighbour relationship between two cells in itself an object, that needs to be referenced by cells 20021 and 20022? Changing the BCC value on one cell could then be passed through to the relationship-object, thus updating both cells.
what you should do is store object references to your cells in a hash say $Network and control the cell creation through a factory class, that knows to check the $Network hash for existing cells...
I think the problem described in the first half of your post is a non-problem.
If I run your code:
package GSM::Cell;
use Moose;
has 'ID' => (is => 'ro', required => 1);
has [qw(BCCH NEIGHBOUR)] => (is => 'rw', default => undef);
no Moose;
__PACKAGE__->meta->make_immutable;
1;
package main;
my $a = GSM::Cell->new(ID => 20021, BCCH => 1);
my $b = GSM::Cell->new(ID => 20022, BCCH => 2);
$a->NEIGHBOUR($b);
$b->BCCH(3);
print $a->NEIGHBOUR->BCCH, "\n"; # 3
It prints the updated value, not the old value. It works because $b is an object, and all Perl objects are blessed references. When you run $a->NEIGHBOUR($b) you are already passing a reference; there is no need to pass a reference to a reference.

How do I best make triggered accessors with defaults in Moose?

I have a situation where I'd like to cache some calculations for use
later. Let's say I have a list of allowed values. Since I'm going to
be checking to see if anything is in that list I'm going to want it as
a hash for efficiency and convenience. Otherwise I'd have to grep.
If I'm using Moose it would be nice if the cache was recalculated each
time the list of allowed values is changed. I can do that with a
trigger easy enough...
has allowed_values => (
is => 'rw',
isa => 'ArrayRef',
trigger => sub {
my %hash = map { $_ => 1 } #{$_[1]};
$_[0]->allowed_values_cache(\%hash);
}
);
has allowed_values_cache => (
is => 'rw',
isa => 'HashRef',
);
And the two will stay in sync...
$obj->allowed_values([qw(up down left right)]);
print keys %{ $obj->allowed_values_cache }; # up down left right
Now let's say I want a default for allowed_values, simple enough
change...
has allowed_values => (
is => 'rw',
isa => 'ArrayRef',
trigger => sub {
my %hash = map { $_ => 1 } #{$_[1]};
$_[0]->allowed_values_cache(\%hash);
},
default => sub {
return [qw(this that whatever)]
},
);
...except setting the default doesn't call the trigger. To get it to
DWIM I need to duplicate the caching.
has allowed_values => (
is => 'rw',
isa => 'ArrayRef',
trigger => sub {
$_[0]->cache_allowed_values($_[1]);
},
default => sub {
my $default = [qw(this that whatever)];
$_[0]->cache_allowed_values($default);
return $default;
},
);
sub cache_allowed_values {
my $self = shift;
my $values = shift;
my %hash = map { $_ => 1 } #$values;
$self->allowed_values_cache(\%hash);
return;
}
The Moose docs are explicit about trigger not getting called when
the default is set, but it gets in the way. I don't like the
duplication there.
Is there a better way to do it?
I was recently faced with this, and after asking on the #moose channel, was told to handle it this way:
Mark cache_allowed_values as a lazy_build, have _build_cache_allowed_values reference the current allowed_values, and put a write-trigger on allowed_values that clears cache_allowed_values.
That way, no matter what order the values are asked for or saved, they'll always be right with the least amount of work.
Example:
has cache_allowed_values => (is => 'ro', lazy_build => 1);
sub _build_cache_allowed_values {
return { map { $_ => 1 } #{shift->allowed_values} };
}
has allowed_values => (
is => 'rw',
trigger => sub { shift->clear_cache_allowed_values },
default => ...,
);
I think you really want allowed_values to be a separate data structure with the efficiency and ordering properties you desire. Since it doesn't look like you care about the ordering, why not:
has 'allowed_values' => (
traits => ['Hash'],
isa => HashRef[Bool],
default => sub { +{} },
handles => {
_add_allowed_value => 'set',
remove_allowed_value => 'delete',
value_is_allowed => 'exists',
allowed_values => 'keys',
},
);
method add_allowed_value(Str $value){
$self->_add_allowed_value( $value, 1 );
}
In general, anything not specific to the class being implemented should probably be implemented elsewhere. Making arrays have faster lookup times is not really the job of whatever class you are writing, so it should be implemented elsewhere, and this class should use that class. (In the simple case, like the hash above, maybe it's OK to ignore this rule. But if it were any more complicated, you would definitely want to factor it out.)
Edit:
If you want the user to think this is a list, how about:
use MooseX::Types::Moose qw(Bool ArrayRef HashRef);
use MooseX::Types -declare => ['ListHash'];
subtype ListHash, as HashRef[Bool];
coerce ListHash, from ArrayRef, via { +{ map { $_ => 1 } #$_ } };
has 'allowed_values' => (
# <same as above>
isa => ListHash,
writer => 'set_allowed_values',
coerce => 1,
);
Now you can set allowed_values like:
my $instance = Class->new( allowed_values => [qw/foo bar/] );
$instance->set_allowed_values([qw/foo bar baz/]);
And access them like:
my #allowed_values = $instance->allowed_values;
... if $instance->value_is_allowed('foo');
And modify them:
$instance->remove_allowed_value('foo');
$instance->add_allowed_value('gorch');
This hides any underlying implementation details from the user.
BTW, is building the hash actually and using it significantly faster than a linear scan over 3 elements?