Can I search resultset from within a toolkit template file? - perl

I use Catalyst and put a resultset into the stash for TT to access:
$c->stash->{tournament} = $c->model('DB::Tournament')->find($id);
This class has a relationship with "tournament_participant" which I access from inside a TT page like this:
[% FOREACH participant IN tournament.tournament_participants -%]
problem is, I want to sort the result by a column like this:
[% FOREACH participant IN tournament.tournament_participants.search( {}, { sort_by => 'position' } ) -%]
but the above does not work (nothing is returned). Is this possible to do?

This should do the trick (assuming the relationship really is tournament_participants (which seems a little redundant and ungainly; tournament.participants feels more natural and is easy to change in the result class if desired)–
[% FOR participant IN tournament.search_related("tournament_participants", {}, { sort_by => 'position' } ) -%]
Doc: DBIx::Class::Relationship::Base.

Related

setting variable name with variable at the end - perl

$id = 1;
foreach (#products) {
$productid$id = param(product$id);
++$id;
}
I am trying to loop through the products array and create a new variable for each item in products for a form which uses checkboxes to select products which are for sale. I tried to use $productid$id and $productid"$id" but it returned syntax errors. Is there a way to achieve this?
I am trying to loop through the products array and create a new variable for each item in products.
While it is possible to create a new variable for each item, it's a bad idea. They have to be global, the syntax is ugly, and it's difficult to pass those variables around as a collection. Instead, use a hash or an array. In this case, since your items are indexed by number and there's no gaps an array makes the most sense.
my #product_params;
foreach my $product (#products) {
push #product_params, param($product);
}
For each $product in #products, it will add param($product) to #product_params. For example, the parameter for $products[5] is stored in $product_params[5].
Since this is a one-to-one mapping of #products to its parameters, its easier and faster to do the above with a map. A mapping runs each element of a list through a function to create a new list. $_ contains each element of #products in turn.
my #product_params = map { param($_) } #products;
Variables aren't interpolated in single quotes.
You're resetting the $id in each iteration of the loop.
Update: Your changes invalidated both the bullets above.
my $id = 1;
for my $product (#products) {
$product_id = param("product$id");
++$id;
}
Say you have four checkboxes named
product1
product2
product3
product4
Say product1 and product3 are checked.
The following will place 1 and 3 in #selected_product_ids:
my #selected_product_ids =
map { my ($id) = /^product(\d+)\z/; defined($id) && param($_) ? $id : () }
params();
If you have the list of all existing product ids in #product_ids, the following will do the same:
my #selected_product_ids =
grep { param("product$_") }
#product_ids;

Traversing hashes in Perl based on config

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.

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

DBIx::Class has_many join on substring of a column

I have two class that are linked on
foreign.weirdkey => substr(self.key, 1, 9)
...and cannot for the life of me figure out how to construct the has_many call to indicate this.
The underlying database (a set of Oracle tables) defines no foreign keys, is fixed, and is outside of my control.
I've been through the docs and can't seem to find a syntax that will work within the confines of a manual has_many definition.
Any help would be much appreciated.
something like this should work:
__PACKAGE__->has_many( baubles => 'My::Schema::Result::Thing', sub {
my $args = shift;
return ({
"$args->{foreign_alias}.weirdkey" => \"substr($args->{self_alias}.key, 1, 9)",
},
$args->{self_rowobj} && {
"$args->{foreign_alias}.weirdkey" => substr($args->{self_rowobj}->key, 1, 9)
})
});
Note that I use perl's substr if you have the current row object, so then the join will collapse into merely a where clause and won't use the database for the string munging. Remove that or fiddle with it if you have problems. DBIC_TRACE should make it clear what goes on.
Documentation here: https://metacpan.org/pod/DBIx::Class::Relationship::Base#condition

What is the DBIx::Class syntax for the CASE WHEN ... THEN SQL syntax?

Does anyone know what's the DBIx::Class equivalent of an SQL query such as:
SELECT cdr_id,
CASE WHEN service_id = 'GPRS' THEN 'KB' WHEN service_id = 'SMS' THEN 'SMS' END AS unit
FROM ...
Thanks
my $rs = $schema->resultset( 'table' )->
search_rs( {} ,
{ '+columns' => {
unit =>
\do { "CASE WHEN me.service_id='GPRS' THEN 'KB' " .
"WHEN me.service_id='SMS' THEN 'SMS' END" }
} ) ;
Something along this line should work.
Another way to deal with complex queries is to define them in a DBIx::Class::ResultSource::View like so:
package My::Schema::Result::ComplexQuery
use strict;
use warnings;
use base qw/DBIx::Class::Core/;
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
__PACKAGE__->table('tablename');
__PACKAGE__->result_source_instance->is_virtual(1);
__PACKAGE__->result_source_instance->view_definition(
q[
SELECT cdr_id,
CASE WHEN service_id = 'GPRS' THEN 'KB' WHEN service_id = 'SMS' THEN 'SMS' END AS unit
FROM table
]
);
then you can call it as you would call dbix::classes normally and you'll get a DBIx::Class::ResultSet object (which will not allow updates or delete, though):
my $pruned_cdr = $schema->resultset('ComplexQuery')->search({}, { ... });
The nice thing about this approach is that it allows complex queries (like when you have multiple complex joins or unions, sub selects etc) to be hidden from your code into a ResultSource::View, so you hide the mix of SQL syntax and objects
Create a table "service_units" populated with:
service | unit
--------+-----
GPRS | KB
SMS | SMS
then
SELECT
cdr.cdr_id, service_units.unit
FROM
cdr INNER JOIN service_units ON cdr.service_id = service_units.service
then translate that into DBIx::Class speak.