Traversing hashes in Perl based on config - perl

I need to be able to pluck specific values from data that I receive from different 3rd parties. The data can structured differently depending on the 3rd party. For example:
my $first =
{
email => "joe\#example.com",
firstname => "Joe",
lastname => "Regular",
};
my $second =
{
user => {
e-mail => "joe\#example.com",
firstName => "Joe",
lastName => "Regular",
}
};
I know what the data structure will be for each 3rd party, so I can define that as config. What I want to end up with is
my $email = _magic($first_config,$first);
my $other_email = _magic($second_config,$second);
Any ideas much appreciated.

Build a look-up table. And you can use a dispatch table, hash with values being code references, so that when a party-identification is used as the key the code for that party executes
my %get_value = ( first => \&fetch_first, second => \&fetch_second );
my $party = 'first'; # input via command-line options, STDIN ...
my $email = $get_value{$party}->();
where \&fetch_first is a reference to the subroutine fetch_first. You can also enter it directly, first => sub { ... }, suitable for simple code. See perlreftut, perlref, and perlsub.
There are many ways to carry data in your program, and so to implement the lookup itself.
Here is an illustration, built in steps. It uses the (confirmed) fact that the data is in valid Perl data structures, and for simplicity it specifies the data right in each sub.
sub fetch_first {
my $data = {
email => '...',
firstName => '...',
};
return $data->{email};
}
This only delivers the email address, but we can do better.
Once you dereference a code reference you can also pass arguments
my $first_name = $get_value{$party}->('firstName');
where the subs are now written to use this input to return the required field
sub fetch_first {
my ($query) = #_;
my $data = {
email => '...',
firstName => '...',
};
return $data->{$query};
}
A big weakness of the above is that the calling code must use valid names of keys, so it needs to know the details of implementation of what it is using.
This can be improved, for example by choosing an interface for the call which is then translated in the subs into key names (or via yet another look-up structure). Then you make calls such as
my $email = $get_value{$party}->('email'); # or: 'first', 'last'
and somewhere you have association first => 'firstName' (etc) which subs can look up.
The flexibility is greatly helped by data being set up in a consistent way. The whole thing can also be quite maintainable if the code is organized thoughtfully.
If this grows more complex the solution is to write a class. Then you can build a very nice system.

Related

Runtime error: MongoDB::DatabaseError: bad hint

I am trying to call the hint method on a MongoDB::Cursor object. However, it throwing an exception when it's trying to execute the query. See the code sample below:
sub some_method_which_returns_cursor {
my $cursor = $collection->find($filter);
if ($hint) {
$cursor->hint({‘some_index’ => 1}); #failing here.
}
if ($sort) {
$cursor->sort($sort);
}
return $cursor;
}
Any thoughts as to what's going on and how I can fix this?
Harish asked me via email and I'll repeat my answer here for posterity:
The hint method takes a string when given an index name, or an array reference when given keys/order pairs:
$cursor->hint("some_index"); # by name
$cursor->hint([field1 => 1, field2 => -1]); # by keys
It also takes a hash reference, but don't use that because modern Perls randomize key order when serializing, so your hint may not match an index.

Perl array of hash with an element which might be another array of hash

assume we have this array structure:
push #active_connections, {
token => $token,
pending => VALID_WEBSOCKET,
time=>time(),
badge => 0,
monlist => $monlist,
intlist => $intlist,
last_sent=>{},
platform => $platform
};
I later want to add to last_sent, such that last_sent will have two values, and "id" and a "value" for that ID and this itself will need to be a list.
In other words lets assume I have ids = 1,2,3 and associated values 10,20,30
I want to be able to do:
$active_connections[i]->last_sent{'1'} should return 20
$active_connections[i]->last_sent{'3'} should return 30
How does one set up last_sent to be able to do something like this?
thanks
You don't have to "set up" anything. Just dereference the value in question, and autovivification will do the rest.
$active_connections[$i]->{last_sent}->{1} = 20;
$active_connections[$i]->{last_sent}->{3} = 30;

Can I join the column names with search_related in DBIx?

I have a DBIx Class schema where I have;
A Device that has many Interfaces
An Interface has many Rules Applied
Each Rule has many Rule Entries.
I want to search for all of the Rule Entries for a Particular device name and Rule Name.
I am still learning DBIx so I don’t know if this is even the most efficient way.
I am doing this like so;
my $rs = $self->search( { devicename => ‘DeviceA’ } )->search_related('interfaces')->search_related(’Rules’, { rulename => ‘RuleA’ } )->search_related(‘RuleEntries’, {},
{ columns => [qw/source destination port/], result_class => 'DBIx::Class::ResultClass::HashRefInflator'} );
What I am trying to do is get the ‘RuleName’ as a column of my result set.
at the moment I’m getting all of the Rule Entries for DeviceA with a RuleName on an interface called RuleA, The columns returned are
‘source destination port’.
I want this to look like
‘rulename source destination port’
As you are already restricting the rule name it doesn't make sense to query it from the database.
Besides that you should always search for objects of the type you want to get back, in your case that's rule entries:
my $rs = $schema->resultset('Rule_Entries')->search({
'rel_device.name' => 'DeviceA',
'rel_rule.name' => 'Rule',
},{
columns => [ 'rel_rule.name', 'me.source', 'me.destination', 'me.port' ],
join => { rel_rule => { rel_interface => 'rel_device' }},
});
It seems your doing something very similar what I do: storing firewall rules. You might want to have the rule directly related to the device and the interface being an optional attribute of the rule because some vendors don't have interface specific rules (Checkpoint).

Perl Catalyst: Resultset and Relations

I have two tables in my database and one of the tables is associated with my Accounts table.
So in my Schema Result for Account.pm I added the following line.
__PACKAGE__->has_many('subjects', 'MyApp::DBIC::Schema::Subject', {'foreight.account_id' => 'self.account_id'});
Then in my controller I make a search like this.
$c->stash->{search_results} = $c->model('DB::Account')->search(
{ -or => [
firstname => {like => '%'.$search_term.'%'},
'subjects.subject_title' => {like => '%'.$search_term.'%'},
]
},
{
join => 'subjects',
rows => '3',
},
{
order_by => 'first name ASC',
page => 1,
rows => 10,
}
);
It does not output any errors, but I can't figure out how to output the results on my view file. Is this a correct method of making relations between two tables?
My goal: provided a search_term, search two tables and output the result in view file. My SQL would look something like this:
SELECT FROM Accounts,Subjects WHERE Accounts.firstname=$search_term OR Subjects.subject_title=$search_term LEFT JOIN Subjects ON Accounts.account_id=Subject.account_id
And would want to output the result in view file, as I stated above.
I am fairly new to Perl and some of the documentations don't make that much sense to me, still. So any help and tips are appreciated.
The join looks OK to me, but it would make sense to try a simplified version without the join to check that everything else is OK.
The behaviour of DBIx::Class::ResultSet::search differs depending on the context in which it's called. If it's called in list context then it executes the database query and returns an array of MyApp::DBIC::Schema::Account objects. For example:
my #accounts = $c->model('DB::Account')->search();
In your case you're calling search in scalar context, which means that rather than returning an array it will return a DBIx::Class::ResultSet object (or a subclass thereof), and crucially it won't actually execute a db query. For that to happen you need to call the all method on your resultset. So, assuming you're using the default template toolkit view you probably want something like this:
[% FOREACH search_result IN search_results.all %]
[% search_result.first_name %]
[% END %]
This 'lazy' behaviour of DBIx::Class is actually very useful, and in my opinion somewhat undersold in the documentation. It means you can keep a resultset in a variable and keep executing different search calls on it without actually hitting the DB, it can allow much nicer code in cases where you want to conditionally build up a complex query. See the DBIx::Class::Resultset documentation for further details.
You have error in your query:
Try:
$c->stash->{search_results} = $c->model('DB::Account')->search(
{ -or => [
firstname => {like => '%'.$search_term.'%'},
'subjects.subject_title' => {like => '%'.$search_term.'%'},
]
},
{
join => 'subjects',
order_by => 'firstname ASC',
page => 1,
rows => 10,
}
);

Can I avoid referencing + dereferencing the hash returned from a map operation?

I've got an array of hashes. I want the a list of the values in a key of those hashes based on the uniqueness of another key.
my #obs = ({
value => 'three',
id => 3
},{
value => 'one-2',
id => 1
},{
value => 'one',
id => 1
});
# This works, prints "one\nthree"
say for values %{{ map { $_->{id} => $_->{value} } #obs }};
Can I avoid the reference + dereference bit around the map? At first I tried just calling values directly on the return from map but Perl won't have it:
Type of arg 1 to values must be hash (not map iterator) at script\workbench.pl line 55, near "#obs ;"
The problem is that values really, really wants a hash to operate on. That's because it's special: it clears the place holder used by each. It needs an actual object to clear that on.
You can go one of two ways here. First, if you don't like the ref/deref, you could just pull the creation of the temporary hash out of the one-liner (please pick a better name than %h for your actual code):
my %h = map { $_->{id} => $_->{value} } #obs;
say for values %h;
If you don't want %h to hang around, just drop it into a temporary block:
...code code code...
{
my %h = map { $_->{id} => $_->{value} } #obs;
say for values %h;
}
...code code code...
Another approach could be to emulate what your temporary hash creation and values is doing:
my %seen;
for ( reverse #obs ) { say $_->{value} unless $seen{$_->{id}}++ }
What really matters is what you're going to be doing with this data. If you just need the values of your inverted hash just once, then your one-liner may be the best solution. If you need this data (id & value) later on, then create the actual hash and use that -- don't do this transform more than once just so that you can keep them as one-liners.
Without further context, it's hard to give advice on which approach to take.
If values were to work on a list, it would take every second element of that list. So
say for values %{{ map { $_->{id} => $_->{value} } #obs }};
would be
say for every_odd map { $_->{id} => $_->{value} } #obs;
Now, it's entirely possible to write such a function, but it's simply not needed in this case. One can simply do
say for map { $_->{value} } #obs;
And that simplifies to
say $_->{value} for #obs;
One catch: By not using a hash, you don't eliminate duplicates.