Perl - Array of Objects - perl

Noob question here.
I'm sure the answer will be create objects, and store them in an array, but I want to see if there is an easier way.
In JSON notation I can create an array of objects like so:
[
{ width : 100, height : 50 },
{ width : 90, height : 30 },
{ width : 30, height : 10 }
]
Nice and simple. No arguing that.
I know Perl is not JS, but is there an easier way to duplicate an array of objects, then to create a new "class", new the objects, and push them in an array?
I guess what would make this possible is an object literal type notation that JS provides.
Or, is there another way to store two values, like above? I guess I could just have two arrays, each with scalar values, but that seems ugly...but much easier than creating a separate class, and all that crap. If I were writing Java or something, then no problem, but I don't want to be bothered with all that when I'm just writing a small script.

Here's a start. Each element of the #list array is a reference to a hash with keys "width" and "height".
#!/usr/bin/perl
use strict;
use warnings;
my #list = (
{ width => 100, height => 50 },
{ width => 90, height => 30 },
{ width => 30, height => 10 }
);
foreach my $elem (#list) {
print "width=$elem->{width}, height=$elem->{height}\n";
}

an Array of hashes would do it, something like this
my #file_attachments = (
{file => 'test1.zip', price => '10.00', desc => 'the 1st test'},
{file => 'test2.zip', price => '12.00', desc => 'the 2nd test'},
{file => 'test3.zip', price => '13.00', desc => 'the 3rd test'},
{file => 'test4.zip', price => '14.00', desc => 'the 4th test'}
);
then access it like this
$file_attachments[0]{'file'}
for more information check out this link http://htmlfixit.com/cgi-tutes/tutorial_Perl_Primer_013_Advanced_data_constructs_An_array_of_hashes.php

Pretty much the same way you do it in JSON, in fact, use the JSON and Data::Dumper modules to produce output from your JSON that you could use in your Perl code:
use strict;
use warnings;
use JSON;
use Data::Dumper;
# correct key to "key"
my $json = <<'EOJSON';
[
{ "width" : 100, "height" : 50 },
{ "width" : 90, "height" : 30 },
{ "width" : 30, "height" : 10 }
]
EOJSON
my $data = decode_json($json);
print Data::Dumper->Dump([$data], ['*data']);
which outputs
#data = (
{
'width' => 100,
'height' => 50
},
{
'width' => 90,
'height' => 30
},
{
'width' => 30,
'height' => 10
}
);
and all that is missing is the my

Related

How can I join a nested Perl hash?

I have a Perl hash, where I store information about LUNs. It has the following structure:
my %luns = (
360000 => {
Devices => [
{ Major_Minor => "8:144",
SCSI_Address => "1:0:0:8",
SCSI_Device => "sdj",
SCSI_Host => "host1",
},
{ Major_Minor => "129:48",
SCSI_Address => "3:0:0:8",
SCSI_Device => "sder",
SCSI_Host => "host3",
},
],
DM_Device => "dm-13",
Size => "45G",
WWID => 360000,
},
360001 => {
Devices => [
{ Major_Minor => "70:144",
SCSI_Address => "1:0:1:39",
SCSI_Device => "sddb",
SCSI_Host => "host1",
},
{ Major_Minor => "135:48",
SCSI_Address => "3:0:1:39",
SCSI_Device => "sdij",
SCSI_Host => "host3",
},
],
DM_Device => "dm-53",
Size => "200G",
WWID => 360000,
},
);
How can I use join to get a comma-separated list of all SCSI_Devices, for example, of 360000?
You're working with a Hash of Hash of Array of Hash. To learn how to work with such structures, I recommend reading perldsc - Perl Data Structures Cookbook.
In this instance, the following loop will print out each of your device lists:
for my $id ( sort { $a <=> $b } keys %luns ) {
my #devices = map { $_->{SCSI_Device} } #{ $luns{$id}{Devices} };
print "$id - #devices\n";
}
Outputs:
360000 - sdj sder
360001 - sddb sdij
Live Demo
You say you want a list of values for LUN 360000, so for a start you need
$luns->{36000}
which is another hash with a Devices element, which has an array reference as a value, and DM_Device, Size, and WWID elements, whose values are simple scalars.
So presumably you want the list that is
$luns->{36000}{Devices}
which is an array of references to hashes, each of which has Major_Minor, SCSI_Address, SCSI_Device, and SCSI_Host elements.
It sounds like you want the SCSI_Device element, and map is the ideal tool to help you with this
my #scsi_devices = map { $_->{SCSI_Device} } #{ $luns->{360000}{Devices} };
That last step is a big leap, and it may help to separate it in your code. For instance, you can copy the reference to the list of devices for 360000, like this
my $devices = $luns->{360000}{Devices};
and extract the SCSI_Device from each of the hashes in that array with
my #scsi_devices = map { $_->{SCSI_Device} } #$devices;
Either way, the array reference must be dereferenced and the required element from each hash in that array must be extracted.
To get a CSV record, unless the data may contain commas of double-quotes, you simply need to join the result of that map
print join(',', #scsi_devices), "\n";
output
sdj,sder
Although I think this falls short of what you actually need. If this isn't clear then please ask.

How do I override compare_fields when using Rose::HTML::Form

So I'm trying use Rose::HTML::Form and I want my fields to appear based on 'rank' rather than by name (the default) .
I've written a comparator subroutine:
sub _order_by_rank {
my ($self, $one, $two) = #_;
return $one->rank <=> $two->rank;
};
and referenced it in my form constructor:
Rose::HTML::Form->new(method => 'post', compare_fields => \&_order_by_rank);
But I am then left with:
Can't call method "name" on unblessed reference at /usr/lib/perl5/site_perl/5.8.8/Rose/HTML/Form/Field/Collection.pm line 405.
It seems to call the comparator before I've added anything.
After constructing the form object, I add some fields and then call init_fields:
$form->add_fields(
id => { type => 'hidden', value => "", rank => 0 },
number => { type => 'int', size => 4, required => 1, label => 'Plant Number', rank => 1 },
name => { type => 'text', size => 25, required => 1, label => 'Plant Name', rank => 2 },
...
);
$form->init_fields;
According to the documentation this is something people usually do. What it doesn't explain is how to do it.
Hopefully someone can explain this to me before I have to buy a new keyboard :)
From the documentation it looks as though, rather than passing in a subroutine reference, you need to subclass Rose::HTML::Form and override the compare_fields method.
The default comparison method is Rose::HTML::Form::compare_fields. You have to create subclasses if you want different sorting methods for different forms.
It would help me to explain further if you showed your full code.

Alias the sum of two columns in a DBIx::Class resultset

SELECT me.id, me.date_created, me.date_updated, me.yes,
me.name, me.description, me.currency, me.locked, me.skip,
me.uri_part, me.user_id,
yes + currency as weight
FROM ideas me having ((weight < 5)) order by weight;
How can I generate that query in DBIx::Class without using literal SQL like this:
my $query = $rs->search({},
{
'+select' => \[
'yes + currency as weight',
],
rows => 1,
order_by => { -desc => [qw/weight name/] },
having => {
weight => { '<' => $self->yes + $self->currency },
},
});
use Data::Dumper;
warn Dumper($query->as_query);
I tried using -as, however, it seems to only be useful for working with columns generated from functions, as this:
'+select' => {
'yes + currency', '-as' => 'weight'
}
generates an error
"Odd number of elements in anonymous hash at
/data/TGC/lib/TGC/DB/Result/Idea.pm line 105, line 1000.
DBIx::Class::SQLMaker::_recurse_fields(): Malformed select argument -
too many keys in hash: SCALAR(0xbf14c40),weight"
Probably the most idiomatic thing I can think of in SQL Abstract expression, without straining too hard:
#!/usr/bin/env perl
use Modern::Perl;
use MySchema;
use Data::Dumper;
my $schema = MySchema->connect('dbi:SQLite:example.db');
my $rs = $schema->resultset('Sample')->search(
{
weight => { '<' => 5 },
},
{
'+select' => [
{ '' => \'`me`.`yes` + `me`.`currency`', -as => 'weight' }
]
}
);
say Dumper( $rs->as_query() );
Which is a contrived wrapping of the column names, but it does the job, sort of. Just don't know of any way to abstract the + here. But stil:
'(SELECT me.name, me.yes, me.currency, ( me.yes + me.currency ) AS weight FROM sample me WHERE ( weight < ? ))',
Unless you are just going for idiomatic perl, in which case:
{ '' => \(join " + ", qw/`me`.`yes` `me`.`currency`/), -as => 'weight' }
But either way seems a little contrived considering both forms are longer than the literal string.
Also note that weight is referenced in WHERE and not HAVING which is because that will blow up on various SQL engines.
The '+as' should be at the same level as '+select'
$idea = $rs->search({-and => [
name => { '<' => $self->name },
]},
{
'+select' => [
'yes + currency'
],
'+as' => [qw/weight/],
rows => 1,
order_by => ['weight', 'name'],
having => {
weight => { '<' => $self->yes + $self->currency },
},
})->single;

How can I do a scrolled search on MetaCPAN?

I'm trying to convert this script to use the new Elasticsearch official client instead of the older (now deprecated) ElasticSearch.pm, but I can't get the scrolled search to work. Here's what I've got:
#! /usr/bin/perl
use strict;
use warnings;
use 5.010;
use Elasticsearch ();
use Elasticsearch::Scroll ();
my $es = Elasticsearch->new(
nodes => 'http://api.metacpan.org:80',
cxn => 'NetCurl',
cxn_pool => 'Static::NoPing',
#log_to => 'Stderr',
#trace_to => 'Stderr',
);
say 'Getting all results at once works:';
my $results = $es->search(
index => 'v0',
type => 'release',
body => {
filter => { range => { date => { gte => '2013-11-28T00:00:00.000Z' } } },
fields => [qw(author archive date)],
},
);
foreach my $hit (#{ $results->{hits}{hits} }) {
my $field = $hit->{fields};
say "#$field{qw(date author archive)}";
}
say "\nUsing a scrolled search does not work:";
my $scroller = Elasticsearch::Scroll->new(
es => $es,
index => 'v0',
search_type => 'scan',
size => 100,
type => 'release',
body => {
filter => { range => { date => { gte => '2013-11-28T00:00:00.000Z' } } },
fields => [qw(author archive date)],
},
);
while (my $hit = $scroller->next) {
my $field = $hit->{fields};
say "#$field{qw(date author archive)}";
} # end while $hit
The first search, where I'm just getting all the results in 1 chunk, works fine. But the second search, where I'm trying to scroll through the results, produces:
Using a scrolled search does not work:
[Request] ** [http://api.metacpan.org:80]-[500]
ActionRequestValidationException[Validation Failed: 1: scrollId is missing;],
called from sub Elasticsearch::Transport::try {...}
at .../Try/Tiny.pm line 83. With vars: {'body' =>
'ActionRequestValidationException[Validation Failed: 1: scrollId is missing;]',
'request' => {'path' => '/_search/scroll','serialize' => 'std',
'body' => 'c2Nhbjs1OzE3MjU0NjM2MjowakFELUU3VFFibTJIZW1ibUo0SUdROzE3MjU0NjM2NDowakFELUU3VFFibTJIZW1ibUo0SUdROzE3MjU0NjM2MTowakFELUU3VFFibTJIZW1ibUo0SUdROzE3MjU0NjM2MDowakFELUU3VFFibTJIZW1ibUo0SUdROzE3MjU0NjM2MzowakFELUU3VFFibTJIZW1ibUo0SUdROzE7dG90YWxfaGl0czoxNDQ7',
'method' => 'GET','qs' => {'scroll' => '1m'},'ignore' => [],
'mime_type' => 'application/json'},'status_code' => 500}
What am I doing wrong? I'm using Elasticsearch 0.75 and Elasticsearch-Cxn-NetCurl 0.02, and Perl 5.18.1.
I finally got it working with the newer Search::Elasticsearch official client. Here's the short version:
#! /usr/bin/perl
use strict;
use warnings;
use 5.010;
use Search::Elasticsearch ();
my $es = Search::Elasticsearch->new(
cxn_pool => 'Static::NoPing',
nodes => 'api.metacpan.org:80',
);
my $scroller = $es->scroll_helper(
index => 'v0',
type => 'release',
search_type => 'scan',
scroll => '2m',
size => 100,
body => {
fields => [qw(author archive date)],
query => { range => { date => { gte => '2015-02-01T00:00:00.000Z' } } },
},
);
while (my $hit = $scroller->next) {
my $field = $hit->{fields};
say "#$field{qw(date author archive)}";
} # end while $hit
Note that the records are not sorted when you do a scrolled search. I wound up dumping the records into a temporary database and sorting them locally. The updated script is on GitHub.
I don't have a direct answer, but I might have an approach to trouble shooting:
I followed your link to the Elasticsearch::Client and found a scroll() method:
https://metacpan.org/pod/Elasticsearch::Client::Direct#scroll
This method takes scroll and scroll_id as parameters. scroll is the number of minutes that you can keep calling the scroll method before the search expires. scroll_id is a marker to the place where the last call to scroll() ended.
$results = $e->scroll(
scroll => '1m',
scroll_id => $id
);
Elasticsearch::Scroll is an object oriented wrapper around scroll() which hides scroll and scroll_id.
I would run perl -d on your script, and step in to $scroller->next and follow that as far down the rabbit hole as you can. Something in there is trying a search which should be populating scroll_id or scrollId and is failing.
My description here is admittedly pretty rough... I ran across an accurate description of what the scroll id is and does during my googling, but I can't seem to find it again.

Why am I getting a "Odd number of elements in anonymous hash" warning in Perl?

Help, I'm trying to create a new post in my wordpress blog with custom fields using the following perl script using metaweblogAPI over XMLRPC, but there seems to be an issue with the custom fields. Only the second custom field (width) ever seems to get posted. Can't get the "height" to publish properly. When I add another field, I get the "Odd number of elements in anonymous hash" error. This has got to be something simple - would someone kindly sanity check my syntax? Thanks.
#!/usr/bin/perl -w
use strict;
use RPC::XML::Client;
use Data::Dumper;
my $cli=RPC::XML::Client->new('http://www.sitename.com/wp/xmlrpc.php');
my $appkey="perl"; # doesn't matter
my $blogid=1; # doesn't matter (except blogfarm)
my $username="Jim";
my $passwd='_____';
my $text=<<'END';
This is the post content...
You can also include html tags...
See you!
END
my $publish=0; # set to 1 to publish, 0 to put post in drafts
my $resp=$cli->send_request('metaWeblog.newPost',
$blogid,
$username,
$passwd,
{
'title' => "this is doodoo",
'description' => $text,
'custom_fields' => {
{ "key" => "height", "value" => 500 },
{ "key" => "width", "value" => 750 }
},
},
$publish);
exit 0;
While techically valid syntax, it's not doing what you think.
'custom_fields' => {
{ "key" => "height", "value" => 500 },
{ "key" => "width", "value" => 750 }
},
is roughly equivalent to something like:
'custom_fields' => {
'HASH(0x881a168)' => { "key" => "width", "value" => 750 }
},
which is certainly not what you want. (The 0x881a168 part will vary; it's actually the address where the hashref is stored.)
I'm not sure what the correct syntax for custom fields is. You can try
'custom_fields' => [
{ "key" => "height", "value" => 500 },
{ "key" => "width", "value" => 750 }
],
which will set custom_fields to an array of hashes. But that may not be right. It depends on what send_request expects.