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;