L3TDE-IRC/lib/L3TDE/Model.pm

214 lines
6.4 KiB
Perl

package L3TDE::Model;
use v5.34.1;
use strict;
use warnings;
use List::AllUtils qw/none any/;
use JSON;
use L3TDE::DB;
use Moo::Role;
requires 'table';
requires 'not_defaulted_fields';
requires 'defaulted_fields';
requires 'jsonb_fields';
requires 'find_fields';
requires 'id_fields';
sub _dbh {
return L3TDE::DB->connect;
}
sub create {
my $class = shift->_get_class;
my $dbh = $class->_dbh;
my %params = @_;
my $not_defaulted_fields = $class->not_defaulted_fields;
my $jsonb_fields = $class->jsonb_fields;
my $defaulted_fields = $class->defaulted_fields;
my @insert_fields = ( @$not_defaulted_fields, @$jsonb_fields );
my @fields = ( @insert_fields, @$defaulted_fields );
my $table = $class->table;
my $query = "INSERT INTO @{[$class->table]} (";
$query .= join ',', @insert_fields;
$query .= ") VALUES (";
$query .= join ',',
(
( map { '?' } @$not_defaulted_fields ),
( map { '?::jsonb' } @$jsonb_fields )
);
$query .= ") ";
$query .= $class->_generate_returning;
$query .= ';';
my $result = $dbh->selectrow_hashref(
$query,
{},
(
map {
my $param = $params{$_};
die "No $_ in @{['%params']} for $class" if !defined $param;
if ( $class->_is_jsonb($_) ) {
$param = encode_json($param);
}
$param;
} @insert_fields
)
);
if ( !defined $result ) {
die "Unable to create $class with args "
. Data::Dumper::Dumper \%params;
}
return $class->_result_to_object($result);
}
sub _generate_returning {
my $class = shift->_get_class;
my $returning = "RETURNING ";
$returning .= $class->_to_select_fields;
return $returning;
}
sub _to_select_fields {
my $class = shift->_get_class;
my $not_defaulted_fields = $class->not_defaulted_fields;
my $jsonb_fields = $class->jsonb_fields;
my $defaulted_fields = $class->defaulted_fields;
my @insert_fields = ( @$not_defaulted_fields, @$jsonb_fields );
my @fields = ( @insert_fields, @$defaulted_fields );
my $select .= join ',', @fields;
return $select;
}
sub _is_field_to_update {
my $class = shift->_get_class;
my $field = shift;
my $jsonb_fields = $class->jsonb_fields;
my $not_defaulted_fields = $class->not_defaulted_fields;
my $defaulted_fields = $class->defaulted_fields;
my @fields = ( @$not_defaulted_fields, @$defaulted_fields, @$jsonb_fields );
return any { $field eq $_ } (@fields);
}
sub update {
my $self = shift;
my $fields_to_update = shift;
my $table = $self->table;
my $dbh = $self->_dbh;
my $not_defaulted_fields = $self->not_defaulted_fields;
my $jsonb_fields = $self->jsonb_fields;
my $defaulted_fields = $self->defaulted_fields;
my $id_fields = $self->id_fields;
for my $field_to_update (@$fields_to_update) {
die "$field_to_update does not exists in @{[$self->_get_class]}"
if !$self->_is_field_to_update($field_to_update);
die "$field_to_update is not a method in @{[$self->_get_class]}"
if !$self->can($field_to_update);
}
my $query = "UPDATE $table SET ";
$query .= join ',', map {
my $key = $_;
my $return = "$key=?";
$return .= '::jsonb' if $self->_is_jsonb($key);
$return
} @$fields_to_update;
$query .= " WHERE ";
$query .= join 'AND', map { "$_=?" } @$id_fields;
$query .= " ";
$query .= $self->_generate_returning;
$query .= ";";
my $result = $dbh->selectrow_hashref( $query, {},
( map { $self->$_ } ( @$fields_to_update, @$id_fields ) ) );
return $self->_result_to_object($result);
}
sub _is_jsonb {
my $class = shift->_get_class;
my $key = shift;
my $jsonb_fields = $class->jsonb_fields;
return any { $key eq $_ } @$jsonb_fields;
}
sub _result_to_object {
my $self = shift;
my $class = $self->_get_class;
my $result = shift;
for my $key ( keys %$result ) {
$result->{$key} = decode_json $result->{$key} if $self->_is_jsonb($key);
}
return $class->new(%$result);
}
sub _get_class {
my $self = shift;
return $self if !ref $self;
return ref $self;
}
sub _validate_find_fields {
my $class = shift->_get_class;
my $fields_to_search = shift;
my $find_fields = $class->find_fields;
for my $field_to_search (@$fields_to_search) {
if ( none { return $_ eq $field_to_search } @$find_fields ) {
die
"$field_to_search is not declared in the list of searchable fields for $class.";
}
}
return 1;
}
sub _generate_select {
my $class = shift->_get_class;
my $fields_to_search = shift;
my $table = $class->table;
my $query = "SELECT @{[$class->_to_select_fields]} FROM $table";
if (@$fields_to_search) {
$query .= " WHERE ";
$query .= join 'AND', map { '$_=?' } @$fields_to_search;
}
return $query;
}
sub find_one {
my $class = shift->_get_class;
my %params = @_;
my $dbh = $class->_dbh;
my @fields_to_search = sort { $a cmp $b } keys %params;
$class->_validate_find_fields(\@fields_to_search);
my $query = $class->_generate_select( \@fields_to_search );
$query .= ' LIMIT 1;';
my $result =
$dbh->selectrow_hashref( $query, {}, @params{@fields_to_search} );
return $class->_result_to_object($result);
}
sub find {
my $class = shift->_get_class;
my %params = @_;
my $page = delete $params{page} // 0;
my $dbh = $class->_dbh;
my @fields_to_search = sort { $a cmp $b } keys %params;
$class->_validate_find_fields(\@fields_to_search);
my $query = $class->_generate_select( \@fields_to_search );
$query .= ' OFFSET ? * 10 LIMIT 10;';
my $result =
$dbh->selectall_arrayref( $query, {Slice => {}}, @params{@fields_to_search}, $page );
my @return;
for my $row (@$result) {
push @return, $class->_result_to_object($row);
}
return \@return;
}
1;