I am trying to create perl object (and its associated attributes), based on some conditions (for example the version of the object), a certain attribute is not needed (or invalid otherwise). How do I create the object, without having to specify the object twice (once for each condition).
my $obj = myobject->new (
attr1 => value1,
attr2 => value2,
attr3 => valuen,
)
Can I for example, define a variable and assign it different value based on the condition? Like this:
my $attribute = (condition)? "":'attr4 => value4';
and simply define the object like this:
my $obj = myobject->new (
attr1 => value1,
attr2 => value2,
attr3 => valuen,
$attribute,
)
This obviously will err out. But you get my idea. How do I make this work?
The arguments to new are just a list — just like the arguments to anything, in fact. You can do what you want with an array instead of a string.
my #attribute = $condition ? () : (attr4 => value4);
my $obj = MyObject->new(
attr1 => value1,
attr2 => value2,
attr3 => value3,
#attribute
);
You can also use a hash if order doesn't matter (which it usually doesn't for constructors) — whatever makes your code better.
I think my approach would be to build a hash of parameters, and pass it to the constructor once it was complete
It would look like this. I've wrapped the whole thing in a do block to limit the scope of %attr; it's not essential. of course condition has to be a compilable Perl expression
my $obj = do {
my %attr = (
attr1 => 'value1',
attr2 => 'value2',
attr3 => 'valuen',
);
$attr{attr4} = 'value4' if condition;
MyObject->new(%attr);
};
Related
I am trying to deep merge the lookup 'default_value' or 'default_values_hash' with the hash returned from a lookup. It will not merge and the default_value only appear to take effect if the hiera title isn't found at all. I cannot set resource defaults here as the values returned are later processed and not actual resource keys yet.
I've tried numerous variations including 'default_value', 'default_values_hash'. I'm seeking a way to just set a default hash in the manifest and have it deep merge with hiera to create a larger hash.
Manifest:
class test (
Hash $result = lookup('test::my_hash', {merge => 'deep', default_values_hash => {foo => 'bar', this => 'that', him => 'her'}}),
){
notice($result)
}
include test
Hiera:
---
test::my_hash:
foo: 'nobar'
this: 'then'
desired result (deep merge):
{ foo => 'nobar', this => 'then', him => 'her' }
actual result (returns hiera hash only):
{ foo => 'nobar', this => 'then' }
UPDATE:
I got it working with the code below. Still interested if anyone has a better solution.
class test (
$stuff = {
foo => 'bar',
this => 'that',
him => 'her'
},
Hash $result = deep_merge($stuff, lookup('test::my_hash')),
){
notice($result)
}
Unfortunately, that is the way lookup works. The default value is only used if no other value is found. The documentation for the default in lookup says
If present, lookup returns this when it can’t find a normal value. Default values are never merged with found values.
Your version using the deep_merge function from stdlib appears to be the best solution.
class foo {
$default_foo_attribute = {
foo => 'bar',
this => 'that',
him => 'her',
}
$attribute = deep_merge($default_foo_attribute,
lookup('foo::attribute',
Hash[String, String],
'deep',
{})
notice($attribute)
}
The following codes make me so confused, I can't find any related knlowledge about the syntax "has ,is ,default, lazy". Can anybody make a detailed explain for me, best wishes.
has 'absolute_E' => (is => 'rw', default => sub {0} );
has 'retract_speed_mm_min' => (is => 'lazy');
has 'retract_speed_mm_min' => (is => 'lazy');
Judging by this line, this is probably a Moo class. To confirm this, have a look near the top of the file, and you should see something like use Moo.
Moo is an object-oriented framework for Perl. I'll assume you understand OO concepts.
Some historical background: Perl 5 has built-in OO capabilities, however it can get a little cumbersome at times. Then Moose came around as an improved way of OOP in Perl. But Moose was also quite heavy, with a compile-time cost, so Moo (and also Mouse just before it) came after that as something of a lighter-weight subset of Moose.
has is for defining attributes in your class.
has 'absolute_E' => ( is => 'rw', default => sub {0} );
This defines an attribute named absolute_E.
is => 'rw' means it is readable and writable, which means you can do this:
my $value = $obj->absolute_E; # gets the value
$obj->absolute_E($value); # sets the value
When you instantiate the object, you can supply a value for the attribute:
my $obj = My::Class->new( absolute_E => 5 );
But if you don't supply anything then absolute_E is set to 0 by default.
This second attribute has a few more things:
has 'retract_speed_mm_min' => (is => 'lazy');
This is short form for:
has 'retract_speed_mm_min' => (
is => 'ro',
lazy => 1,
builder => '_build_retract_speed_mm_min'
);
This attribute is readonly which means you can't change its value after construction. But you can supply a value at construction as before.
The builder is another way of providing a default value. It requires the class to have a separate method named _build_retract_speed_mm_min that should return the default value.
lazy works with builder. It means that the attribute should not be set by the builder until it the attribute is used. The delay may be used because the builder depends on other attributes in order to build this attribute's value.
There's a lot more in Moo and Moose. I would suggest reading http://modernperlbooks.com/books/modern_perl_2014/07-object-oriented-perl.html and https://metacpan.org/pod/Moose::Manual and https://metacpan.org/pod/Moo.
That code basically equals
has ('absolute_E', 'is', 'rw', 'default', sub {0} );
has ('retract_speed_mm_min', 'is', 'lazy');
And has looks like a user-defined subroutine.
=> is almost the same as ,:
The => operator is a synonym for the comma except that it causes a word on its left to be interpreted as a string if it begins with a letter or underscore and is composed only of letters, digits and underscores.
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});
I realise this is probably a basic misunderstanding of some part of perl or Moose on my part, but I don't seem to be able to return an ArrayRef from a default method:
has '_directories' => (
is => 'ro',
isa => 'ArrayRef[Str]',
lazy => 1,
init_arg => undef,
default => method {return File::Spec->splitdir($self->relativeDirectory)});
gives:
Attribute (_directories) does not pass the type constraint because:
Validation failed for 'ArrayRef[Str]' with value 3
How do I sort this out?
splitdir returns list, not arrayref. You can construct arrayref from list using [] constructor:
default => method {return [ File::Spec->splitdir($self->relativeDirectory) ] },
I'm trying to create an OR logic query using Class::DBI/Class::DBI::AbstractSearch. My code looks something like this:
my $results = Example::CDBI::Quote->search_where(
{ field_1 => {'like', $search_string},
field_2 => {'like', $search_string}},
{logic => 'or'}
);
According to the documentation this should work. It says that the information is passed to SQL::Abstract::Limit, which shows as taking the logic parameter. Unfortunately, MySQL shows the following in the query log (edited for brevity, and assuming a search of "123"):
SELECT * FROM quote WHERE ((field_1 LIKE '123' AND field_2 LIKE '123' ))
I have trying changing 'or' to 'OR' (silly, but worth a shot) which did not work. I also tried hunting down the logic in SQL::Abstract::Limit, but this operator is being passed to SQL::Abstract instead.
How do I get SQL::Abstract::Limit to accept OR logic from Class::DBI?
How Class::DBI calls SQL::Abstract::Limit
I was able to determine how SQL::Abstract::Limit is being constructed. I put values in instead of the variable names so it is easier to read.
my $sql = SQL::Abstract::Limit->new({'logic' => 'OR'});
my($phrase, #bind) = $sql->where(
{'field_1'=>{'like' => '123'},'field_2'=>{'like'=>'123'}},
undef, undef, undef);
You can apply OR locally like this:
use SQL::Abstract;
my $sql = SQL::Abstract->new;
my ($stmt, #bind) = $sql->where(
{ -or => [ { field_1 => { 'like', 'John' }},
{ field_2 => { 'like', 'John' }},
],
}, []);
gives in $stmt:
WHERE ( ( field_1 LIKE ? OR field_2 LIKE ? ) )
The logic property can be set in SQL::Abstract constructor, but I don't have idea how to propagate from Class::DBI.
Edit: I don't know if it is bug or feature, but it the operators changed by logic clause seems apply only when you define with arrayrefs. With hashrefs, you get always AND:
my $sql_and = SQL::Abstract::Limit->new(logic => 'AND');
my $sql_or = SQL::Abstract::Limit->new(logic => 'OR');
say $sql_and->where(['field_1'=>{'like' => '123'},'field_2'=>{'like'=>'123'}]);
# WHERE ( ( field_1 LIKE ? AND field_2 LIKE ? ) )
say $sql_or->where (['field_1'=>{'like' => '123'},'field_2'=>{'like'=>'123'}]);
# WHERE ( ( field_1 LIKE ? OR field_2 LIKE ? ) )
Or, to work with Class::DBI:
my $results = Example::CDBI::Quote->search_where(
[ field_1 => {'like', $search_string},
field_2 => {'like', $search_string}],
{logic => 'or'}
);