DBIx::Class update Inflate column with function - perl

I tried to emulate this SQL in DBIx::Class against the update_or_new function.
UPDATE user SET lastseen = GREATEST( lastseen, ?::timestamp ) WHERE userid = ?
It gives an error on inflate column saying it is unable to invoke is_infinity on undef .
$schema->resultset('user')->update_or_new( {
userid => 'peter',
lastseen => \[ 'GREATEST( lastseen, ?::timestamp )', DateTime->from_epoch(epoch => 1234) ]
} );
I guess this is because the InflateColumn::DataTime does not expect a function there. Is there any clean workaround for this issue?

This is a bug in DBIx::Class ( addressed here: https://github.com/dbsrgits/dbix-class/pull/44 ) and the fix is merged. It should be fine on the next release.
That said, if you're using DBIx::Class <= 0.08270...
You're using update_or_new, but the function only makes sense if the row exists already:
GREATEST( lastseen, ?::timestamp ) lastseen is undefined if the row doesn't exist yet.
I read through the source+docs a bunch and cannot find a way to sidestep the InflateColumn code and still have bind values. You can pass in literal SQL with a scalar ref ( \'NOW()' ) but not an array ref.
Your best bet would be to use the ResultSet's update method instead, which does not 'process/deflate any of the values passed in. This is unlike the corresponding "update" in DBIx::Class::Row.'
my $dtf = $schema->storage->datetime_parser; #https://metacpan.org/pod/DBIx::Class::Storage::DBI#datetime_parser
my $user_rs = $schema->resultset('User')->search({ userid => 'peter' });
my $dt = DateTime->from_epoch(epoch => 1234);
#select count(*) where userid = 'peter';
if( $user_rs->count ) {
$user_rs->update(
lastseen => \[ 'GREATEST( lastseen, ? )', $dtf->format_datetime($dt) ]
);
} else {
$user_rs->create({ lastseen => $dt });
}

Related

DBIx::Class and overloading accessors

(Similar to, but with more concrete details that, #11526999)
My Result Classes have been built using dbicdump, however I wish to overload the default accessor for a date field.
Works, but a bodge
To hackytest my idea, I simply added an accessor attribute to the created date key of the add_columns call:
__PACKAGE__->add_columns(
"stamp_id",
{
data_type => "integer",
is_auto_increment => 1,
is_nullable => 0,
sequence => "timestamp_stamp_id_seq",
},
"date",
{ data_type => "date", is_nullable => 0, accessor => '_date' },
);
... and created my accessor routine below the Schema::Loader checksum line:
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:nB5koMYAhBwz4ET77Q8qlA
sub date {
my $self = shift;
warn "overloaded date\n"; # Added for debugging
my $date;
# The date needs to be just the date, not the time
if ( #_ ) {
$date = shift;
if ( $date =~ /^([\d\-]+)/ ) {
$date = $1
}
return $self->_date($date)
}
# Fetch the column value & remove the time part.
$date = $self->_date;
if ( $date =~ /^([\d\-]+)/ ) {
$date = $1
}
return $date;
}
This works, as it returns an expected 2014-10-04, but is a bodge.
Do it the right way
The problem is that I've hacked the checksum'd code, so I can't neatly re-generate my Class objects.
Reading ResultSource and the CookBook the correct approach appears to be:
Have the ResultSource built by dbicdump as standard:
__PACKAGE__->add_columns(
"stamp_id",
{
data_type => "integer",
is_auto_increment => 1,
is_nullable => 0,
sequence => "timestamp_stamp_id_seq",
},
"date",
{ data_type => "date", is_nullable => 0 },
);
.... add a change the accessor below the line, using the + to indicate it's an alteration to an existing definition:
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:nB5koMYAhBwz4ET77Q8qlA
__PACKAGE__->add_columns(
"+date", { accessor => '_date' },
);
.... use the overload method as before
Not working.
I've double-checked my spelling, I've tried add_column rather than add_columns, and I've tried putting the second add_columns to directly below the first - all to now avail.... the code uses the default accessor, and returns 2014-10-04T00:00:00
How do I over-ride the default accessor, so I can use my own method?
Thankee...
What you need here is a col_accessor_map passed in as a loader option.
col_accessor_map => {
table_name => {
date => _date,
}
}
You can pass loader options to dbicdump with -o.
$ dbicdump -o col_accessor_map="{ table_name => { date => _date } }" ... other options ...
(Replace table_name above with the name of your table - that's obvious, right?)
Update: This was posted untested, and when I finally got round to testing it, I found it didn't work. After a conversation with the author on IRC I was told that the col_accessor_map option doesn't support this nested hash approach, so if you wanted to use this approach you would need to use a coderef.
However, the author also agreed that adding this support would be a good idea and I've just got back from lunch to find this Github commit which adds the feature. I don't know how soon it will get to CPAN though.
This may be the first time that CPAN has been updated to make a SO answer correct :-)
At a different level of abstraction I believe you could use a method modifier
use Class::Method::Modifier; # or Moose/Moo
around date => sub {...};

How much magic can DBIx::Class::Row::set_columns do?

Is DBIx::Class::Row::set_columns clever enough to update prefetched child rows?
I've tried it, and it doesn't seem to be. I'm probably expecting too much magic.
I did something like this:
my $data = {
id => 1,
date => '2015-06-27',
# etc.
invoice_lines => [{
id => 101,
# etc.
]},
};
my $rs = $schema->resultset('Invoice')->search(
{ 'me.id' => $id },
{ prefetch => 'invoice_lines' },
)->first;
$rs->set_columns($data);
and got something like this:
SELECT me.id, me.date, ..., invoice_lines.id, ... FROM invoices me LEFT
JOIN invoice_lines invoice_lines ON invoice_lines.invoice_id = me.id WHERE ( me.id = ? ) ORDER BY me.id: '1'
Mojo::Reactor::EV: Read failed: DBIx::Class::Row::get_column(): No such column 'invoice_lines' on MyProg::DB::Schema::Result::Invoice at /home
/chris/stuff/Invoices.pm line 289
It thinks 'invoice_lines' is just another column, rather than a relationship. The relationships are working properly when inserting into or reading from the database, so I haven't included all the gory details.
This feature isn't core although it is planned.
Currently there is the DBIx::Class::ResultSet::RecursiveUpdate module that implements this.

How do i pass a variable for the query in a get Manager call?

I am trying to make a simple Rose DB call:
$id = xyz;
$name = "company";
DataB::testTable::Manager->get_testTable( query =>[ id => $id, name => $name ] );
in it possible to not have the whole query written every time, and declare it like a string variable such that i can just call
DataB::testTable::Manager->get_testTable( query =>[ $query ] );
where $query = qq { id => $id , name => $name };
Please Help
By what I understood from your question, I am giving this answer.
Try this one.
my $myquery = {query =>{ id=>$id, name=>$name }} ;
TGI::testTable::Manager->get_testTable($myquery);
Hope, this gives some idea to you.
Edit for "Hash with Array reference":
my $myquery = [ id=>$id, name=>$name ] ;
TGI::testTable::Manager->get_testTable(query => $myquery);
check out this : How to pass a a string variable as "query" for get Manager call?
Well actually i figured out how to do that . Its not that complicated. Only thing is RoseDB objects expect an array reference for a query. So something like this works :
my #query = ( id => $id, name => $name );
testDB::testTable::Manager->get_testTable( query => \#query );
Just thought would answer it myself, incase someonelse is looking for a solution to this

How do I access a specific return value from the CDBI::Search function?

I am using a DB::CDBI class for accessing the database in our application. Our project is in object-oriented Perl.
package LT::LanguageImport;
use strict;
use warnings;
use base 'Misk5::CDBI';
__PACKAGE__->table( 'english_text_translation' );
__PACKAGE__->columns( Primary => qw/english language translation/ );
__PACKAGE__->columns( Essential => qw/english language translation/ );
__PACKAGE__->has_a( english => 'LT::EnglishLanguage' );
In one such scenario, I am supposed to check if a row in a table exists. I am using the built-in search API in a CDBI call.
sub find_translation {
my $translation_exists_r_not = $class->search(
english => $english,
language => $language,
translation => $translation
);
return;
}
$translation_exists_r_not is getting the expected values depending on the inputs given in the search. If the row exists, then the _data is updated with the row details.
$translation_exists_r_not = bless({
'_data' => [
{
'language' => 'polish',
'translation' => 'Admin',
'english' => 'admin'
}
],
'_place' => 0,
'_mapper' => [],
'_class' => 'LT::LanguageImport'
},
'Class::DBI::Iterator'
);
If the row desn't exist, then I get a return value like this.
$translation_exists_r_not = bless({
'_data' => [],
'_place' => 0,
'_mapper' => [],
'_class' => 'LT::LanguageImport'
},
'Class::DBI::Iterator'
);
I want to return the value of translation from this sub find_translation depending on the search result. I am not able to get a best condition for this.
I tried copying the _data into an array, but I'm not sure how to proceed further. As _data will be an empty arrayref and another condition it will have a hashref inside the arrayref.
my #Arr = $translation_exists_r_not->{'_data'};
CDBI's search method will return an iterator, because there may be multiple rows returned depending on your criteria.
If you know there can be only one row that matches your criteria, you want to use the retrieve method, i.e.:
if (my $translation_exists_r_not = $class->retrieve(
english => $english,
language => $language,
translation => $translation
)){
return [$translation_exists_r_not->translation,
'Misk5::TranslationAlreadyExists']
}
else {
return [ '', undef ]
}
And if multiple rows can be returned from your search, and you're only interested in the truthiness, then again, don't be rummaging around inside the CDBI::Iterator, but use its methods:
my $translation_exists_r_not = $class->search(
english => $english,
language => $language,
translation => $translation
); # returns an iterator
if ($translation_exists_r_not){
my $first = $translation_exists_r_not->first;
return [ $first->translation, 'Misk5::TranslationAlreadyExists' ]
}
Have a look at perldoc Class::DBI and perldoc Class::DBI::Iterator. CDBI has excellent documentation.
I think I got the solution. Thanks to whoever has tried to solve it.
my #req_array = %$translation_exists_r_not->{_data};
my $length_of_data = '9';
foreach my $elem (#req_array) {
$length_of_data = #{$elem};
}
Now check the length of the array.
if ($length_of_data == 0) {
$error = '';
$result = [undef, $error];
}
Now check if it is one.
if ($length_of_data == 1) {
my #result_array = #{%$translation_exists_r_not->{_data}};
my $translation = $result_array[0]{'translation'};
$error = 'Misk5::TranslationAlreadyExists';
$result = [$translation, $error];
}
return #$result;

Which Perl module should I use to generate a validating CRUD webform?

Has anyone successfully used something like DBIx::Class::WebForm or CatalystX-CRUD to automagically build a self-validating webform from a database table?
I'm imagining a module that reads a database table schema, reads the constraints for each column, and generates some abstract representation of a webform, with fields for error messages, etc. I'm using Catalyst and Plack with a big existing codebase.
I don't want to code up an HTML webform, nor any validation logic. I'm aiming to write as little code as possible, in the style of Ruby on Rails. Which Perl module is best for this?
UPDATE: I've solved the webform side with HTML::FormFu, but it's still clunky mapping the form inputs onto the database, e.g. date_start and date_end both relate to the 'created' column, and comment should match using 'LIKE %foo%', etc. Where's the 'DBICFu'?
UPDATE: This is for a web application, the webform should not look like a database table. I'm not looking for a database management tool.
You can use use HTML::FormHandler::Moose and HTML::FormHandler::Model::DBIC and get some nice forms.
As a simple example:
The form definition:
package MyStats::Form::Datetime ;
use HTML::FormHandler::Moose ;
extends 'HTML::FormHandler::Model::DBIC' ;
use Date::Calc qw(Today_and_Now) ;
has_field 'datetimeid' => ( label => 'ID' ) ;
has_field 'datetime' => ( type => 'Text',
apply => [ { transform => \&transform_dt } ] ,
deflation => \&deflation_dt ,
required => 1 ) ;
has_field 'submit' => ( type => 'Submit' ,
value => 'Speichern' ) ;
# These are the fields of the table datetime
sub transform_dt {
my ( $dt ) = #_ ;
my #d = ( $dt =~ m/(\d{1,2})\.(\d{1,2})\.(\d{4})\s+(\d{1,2}):(\d{1,2})/ ) ;
return sprintf( '%04d-%02d-%02d %02d:%02d:00' , #d[2,1,0,3,4] ) ;
}
sub deflation_dt {
my ( $dt ) = #_ ;
my #d = ( $dt =~ m/(\d{4})-(\d{2})-(\d{2})\s+(\d{1,2}):(\d{1,2})/ ) ;
if( ! #d ) {
#d = Today_and_Now() ;
}
return sprintf( '%02d.%02d.%04d %02d:%02d:00' , #d[2,1,0,3,4] ) ;
}
1 ;
And the usage in a controller:
package MyStats::Controller::Datetime ;
use Moose ;
use namespace::autoclean ;
BEGIN { extends 'Catalyst::Controller' ; }
use MyStats::Form::Datetime ;
has 'form' => ( isa => 'MyStats::Form::Datetime' ,
is => 'rw' ,
lazy => 1 ,
default => \&new_datetime_form ) ;
sub new_datetime_form {
MyStats::Form::Datetime->new( css_class => 'datetimeform' ,
name => 'datetimeform' ) ;
}
...
sub add :Local :Args(0) {
my ( $self , $ctx ) = #_ ;
my $data = $ctx->model( 'MyStatsDB::Datetime' )->new_result( {} ) ;
$ctx->stash( template => 'datetime/add.tt2' ,
form => $self->form ) ;
$ctx->bread_crumb( { name => 'Datum/Zeit eingeben' ,
location => '/datetime/add' } ) ;
$ctx->req->param( 'datetimeid' , undef ) if $ctx->req->param( 'datetimeid' ) ;
return unless $self->form->process( item => $data ,
params => $ctx->req->params ) ;
$ctx->flash( message => 'Neuer Datensatz ' . $data->datetimeid .
' angelegt.' ,
id_add => $data->datetimeid ) ;
$ctx->res->redirect( $ctx->uri_for( '/datetime' ) ) ;
}
...
__PACKAGE__->meta->make_immutable ;
1 ;
Works good.
I've used HTML::FormHandler to generate forms for me in this fashion. It needs some tweaking, but it does 90% of the work for you. Separately DBIx::Class offers a similar tool.
There are a number of crud options on the Catalyst wiki.
It sounds like AutoCrud would fit your needs.