From 0820ab95a5d5a9dcfde586b7260bef407af64adb Mon Sep 17 00:00:00 2001 From: Lari Taskula Date: Thu, 4 Feb 2021 19:28:30 +0000 Subject: [PATCH] Bug 20028: Add Koha::Patron->export The logic behind gathering related data relies on DBIx relationships to Borrower source. This patch does not care about object name terminology - it returns related data in a hashref containing keys of DBIx source class names, such as Borrower. Return example: { "Borrower" => Koha::Patron, "BorrowerMessagePreference" => DBIx::Class::ResultSet, "ReturnClaim" => Koha::Checkouts::ReturnClaims } This has the benefit of giving caller the ability to format response as they want. For example, you can then easily use the "unblessed" or "to_api" representation of the object. To test: 1. prove t/db_dependent/Koha/Patron.t 2. Observe success Sponsored-by: The National Library of Finland --- Koha/Patron.pm | 93 +++++++++++++++++++++++++++++++ t/db_dependent/Koha/Patron.t | 104 ++++++++++++++++++++++++++++++++++- 2 files changed, 196 insertions(+), 1 deletion(-) diff --git a/Koha/Patron.pm b/Koha/Patron.pm index 8606ab0bb8..731006f2e9 100644 --- a/Koha/Patron.pm +++ b/Koha/Patron.pm @@ -1550,6 +1550,99 @@ sub add_extended_attribute { } +=head3 export + + my $export = $patron->export() + + Gathers patron's related data by checking DBIx relationships. + + Returns a hashref where keys are DBIx source names, and values are either + an instance of Koha::Objects or DBIx::Class:ResultSet, or when source + equals "Borrower", a Koha::Object. + + Koha::Objects is returned for sources that have a respective Koha::Object, + and for other sources DBIx::Class::ResultSet. + + Return example: + + { + "Borrower" => Koha::Patron, + "BorrowerMessagePreference" => DBIx::Class::ResultSet, + "ReturnClaim" => Koha::Checkouts::ReturnClaims + } + +=cut + +sub export { + my ( $self, $params ) = @_; + + my $schema = Koha::Database->new()->schema(); + + my $export = {}; + my $relationships = {}; + + my $logger = Koha::Logger->get; + my $result_source = $self->_result()->result_source; + + foreach my $rel ( $self->_result()->relationships() ) { + my $related_source = $result_source->related_source( $rel ); + + # Skip all the "[borrower] belongs_to" relationships + my $info = $result_source->relationship_info( $rel ); + if ( $info->{'attrs'}->{'is_depends_on'} ) { + next; + } + + # 'cond' has format foreign.columnname, remove ^foreign. from it + ( my $rel_col = ( keys %{ $info->{'cond'} } )[0] ) =~ s/^foreign\.//; + + my $src_name = $related_source->source_name; + + $relationships->{$src_name} = [] unless $relationships->{$src_name}; + push @{ $relationships->{$src_name} }, $rel_col; + } + + foreach my $src_name ( keys %$relationships ) { + my $related_source = $schema->source( $src_name ); + my $res; + my $rs = $related_source->resultset; + my $search; + + if ( scalar @{ $relationships->{$src_name} } > 1 ) { + $search = { '-or' => [] }; + foreach my $rel_col ( values @{ $relationships->{$src_name} } ) { + push @{ $search->{'-or'} }, $rel_col; + push @{ $search->{'-or'} }, $self->borrowernumber; + } + } else { + $search = { $relationships->{$src_name}->[0] => $self->borrowernumber }; + } + + if ( $related_source->result_class->can('koha_objects_class') ) { + my $koha_object = $related_source->result_class->koha_objects_class; + + if ( ! Module::Load::Conditional::can_load( + modules => { $koha_object => undef} )) { + Koha::Exceptions::Exception->throw( + error => "Could not load '$koha_object'" + ); + } + + $res = $koha_object->search( $search ); + } else { + $res = $rs->search( $search ); + } + + next unless $res->count; + + $export->{$src_name} = $res if $res; + } + + $export->{'Borrower'} = $self; + + return $export; +} + =head3 extended_attributes Return object of Koha::Patron::Attributes type with all attributes set for this patron diff --git a/t/db_dependent/Koha/Patron.t b/t/db_dependent/Koha/Patron.t index c5a2cbfd90..34e9509679 100755 --- a/t/db_dependent/Koha/Patron.t +++ b/t/db_dependent/Koha/Patron.t @@ -19,7 +19,7 @@ use Modern::Perl; -use Test::More tests => 7; +use Test::More tests => 8; use Test::Exception; use Test::Warn; @@ -78,6 +78,108 @@ subtest 'add_guarantor() tests' => sub { $schema->storage->txn_rollback; }; +subtest 'export' => sub { + my $limit_data = 5; # build this many random test objects + my $generated_data = 0; + + plan tests => $limit_data * 2; + + my @related_sources = _get_related_sources(); + + $schema->storage->txn_begin; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + } + ); + + my $test_objects = { + 'Borrower' => $patron->unblessed, + }; + + my $result_source = Koha::Patron->new->_result()->result_source; + foreach my $rel ( Koha::Patron->new->_result()->relationships() ) { + if ( $generated_data >= $limit_data-1 ) { + last; + } + + my $related_source = $result_source->related_source( $rel ); + my $source_name = $related_source->source_name; + + my $info = $result_source->relationship_info( $rel ); + + # We are not interested in the "belongs_to" relationship of borrowers. + # These are tables like branches, categories and sms_provider. + if ( $info->{'attrs'}->{'is_depends_on'} ) { + next; + } + + ( my $rel_col = ( keys %{ $info->{'cond'} } )[0] ) =~ s/^foreign\.//; + + # Generate test data into related tables + my $built; + if ( $related_source->result_class->can('koha_objects_class') ) { + $built = $builder->build_object( + { + class => $related_source->result_class->koha_objects_class, + value => { $rel_col => $patron->borrowernumber } + } + ); + $built = $built->unblessed; + } else { + $built = $builder->build( + { + source => $source_name, + value => { $rel_col => $patron->borrowernumber } + } + ); + } + + $test_objects->{$source_name} = [] unless $test_objects->{$source_name}; + push @{ $test_objects->{$source_name} }, $built; + $generated_data++; + } + + my $export = $patron->export; + + foreach my $src_name ( keys %$test_objects ) { + my $related_source = $schema->source( $src_name ); + + ok( exists $export->{$src_name}, "$src_name exists in export" ); + my $res; + if ( $export->{$src_name}->can('unblessed') ) { + $res = $export->{$src_name}->unblessed; + } else { + $export->{$src_name}->result_class('DBIx::Class::ResultClass::HashRefInflator'); + $res = [ $export->{$src_name}->all() ]; + } + + is_deeply( + $res, + $test_objects->{$src_name}, + "$src_name export data matches built test data" + ); + } + + sub _get_related_sources { + my $sources = {}; + my $res_source = Koha::Patron->new->_result()->result_source; + foreach my $rel ( Koha::Patron->new->_result()->relationships() ) { + my $related_source = $res_source->related_source($rel); + my $info = $res_source->relationship_info( $rel ); + next if $info->{'attrs'}->{'is_depends_on'}; + next if $sources->{$related_source->source_name}; + $sources->{$related_source->source_name} = 1; + } + $sources->{'Borrower'} = 1; # add Borrower itself + my @sorted = sort keys %$sources; + return @sorted; + } + + $schema->storage->txn_rollback; +}; + subtest 'relationships_debt() tests' => sub { plan tests => 168; -- 2.25.1