From 4b61b3d340d3a70aebd15161331a7b944337cd28 Mon Sep 17 00:00:00 2001 From: Kyle M Hall Date: Tue, 30 Sep 2014 15:05:30 -0400 Subject: [PATCH] Bug 13019 - Add base classes on which to build Koha objects The idea behind this is to have a pair of base classes on which to build our new generation of Koha objects. Koha::Object is a base class, which in it's most basic form, is to represent a row in a table. For example, Koha::Borrower inherits from Koha::Object. So too could Koha::Biblio and Koha::Item for example. Koha::Objects is to represent a way to fetch and manipulate sets of objects. For example, Koha::Borrowers has a method to get a Koha::Borrower object by id and a method to search for an get a list of Koha::Borrower objects. Right now Koha::Objects has only the essentials but can easily be extended and those enhancements will be passed down to all the child classes based on it. By using these classes as a base, we will add consistency to our code, allow us to keep our code DRY, reduce bugs, and encapsulate our database access among other benefits. Test Plan: 1) Apply this patch 2) prove t/Object.t t/db_dependent/Object.t t/db_dependent/Objects.t --- Koha/Borrower.pm | 52 ++++++++ Koha/Borrowers.pm | 58 +++++++++ Koha/Object.pm | 286 ++++++++++++++++++++++++++++++++++++++++++++ Koha/Objects.pm | 163 +++++++++++++++++++++++++ t/Borrower.t | 57 +++++++++ t/db_dependent/Borrower.t | 70 +++++++++++ t/db_dependent/Borrowers.t | 77 ++++++++++++ 7 files changed, 763 insertions(+), 0 deletions(-) create mode 100644 Koha/Borrower.pm create mode 100644 Koha/Borrowers.pm create mode 100644 Koha/Object.pm create mode 100644 Koha/Objects.pm create mode 100755 t/Borrower.t create mode 100755 t/db_dependent/Borrower.t create mode 100755 t/db_dependent/Borrowers.t diff --git a/Koha/Borrower.pm b/Koha/Borrower.pm new file mode 100644 index 0000000..a266e82 --- /dev/null +++ b/Koha/Borrower.pm @@ -0,0 +1,52 @@ +package Koha::Borrower; + +# Copyright ByWater Solutions 2014 +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use Carp; + +use Koha::Database; + +use base qw(Koha::Object); + +=head1 NAME + +Koha::Borrower - Koha Borrower Object class + +=head1 API + +=head2 Class Methods + +=cut + +=head3 type + +=cut + +sub Type { + return 'Borrower'; +} + +=head1 AUTHOR + +Kyle M Hall + +=cut + +1; diff --git a/Koha/Borrowers.pm b/Koha/Borrowers.pm new file mode 100644 index 0000000..41efdb6 --- /dev/null +++ b/Koha/Borrowers.pm @@ -0,0 +1,58 @@ +package Koha::Borrowers; + +# Copyright ByWater Solutions 2014 +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use Carp; + +use Koha::Database; + +use Koha::Borrower; + +use base qw(Koha::Objects); + +=head1 NAME + +Koha::Borrower - Koha Borrower Object class + +=head1 API + +=head2 Class Methods + +=cut + +=head3 type + +=cut + +sub Type { + return 'Borrower'; +} + +sub ObjectClass { + return 'Koha::Borrower'; +} + +=head1 AUTHOR + +Kyle M Hall + +=cut + +1; diff --git a/Koha/Object.pm b/Koha/Object.pm new file mode 100644 index 0000000..1a39cac --- /dev/null +++ b/Koha/Object.pm @@ -0,0 +1,286 @@ +package Koha::Object; + +# Copyright ByWater Solutions 2014 +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use Carp; +use Encode qw{encode}; + +use Koha::Database; + +=head1 NAME + +Koha::Object - Koha Object base class + +=head1 SYNOPSIS + + use Koha::Object; + my $object = Koha::Object->new({ property1 => $property1, property2 => $property2, etc... } ); + +=head1 DESCRIPTION + +This class must always be subclassed. + +=head1 API + +=head2 Class Methods + +=cut + +=head3 Koha::Object->new(); + +my $object = Koha::Object->new(); +my $object = Koha::Object->new($attributes); + +=cut + +sub new { + my ( $class, $attributes ) = @_; + my $self = {}; + + if ($attributes) { + $self->{_result} = + Koha::Database->new()->schema()->resultset( $class->Type() ) + ->new($attributes); + } + + croak("No type found! Koha::Object must be subclassed!") + unless $class->Type(); + + bless( $self, $class ); + +} + +=head3 Koha::Object->new_from_dbic(); + +my $object = Koha::Object->new_from_dbic($dbic_row); + +=cut + +sub new_from_dbic { + my ( $class, $dbic_row ) = @_; + my $self = {}; + + # DBIC result row + $self->{_result} = $dbic_row; + + croak("No type found! Koha::Object must be subclassed!") + unless $class->Type(); + + croak( "DBIC Result type isn't of the type " . $class->Type() ) + unless ref( $self->{_result} ) eq "Koha::Schema::Result::" + . $class->Type(); + + bless( $self, $class ); + +} + +=head3 $object->Store(); + +Saves the object in storage. +If the object is new, it will be created. +If the object previously existed, it will be updated. + +Returns: + 1 if the store was a success + 0 if the store failed + +=cut + +sub Store { + my ($self) = @_; + + return $self->_Result()->update_or_insert() ? 1 : 0; +} + +=head3 $object->IsStore(); + +Returns true if the object has been previously stored. + +=cut + +sub InStorage { + my ($self) = @_; + + return $self->_Result()->in_storage(); +} + +=head3 $object->IsChanged(); + +Returns true if the object has properties that are different from +the properties of the object in storage. + +=cut + +sub IsChanged { + my ( $self, @columns ) = @_; + + return $self->_Result()->is_changed(@columns); +} + +=head3 $object->Delete(); + +Removes the object from storage. + +Returns: + 1 if the deletion was a success + 0 if the deletion failed + -1 if the object was never in storage + +=cut + +sub Delete { + my ($self) = @_; + + # Deleting something not in storage thows an exception + return -1 unless $self->_Result()->in_storage(); + + # Return a boolean for succcess + return $self->_Result()->delete() ? 1 : 0; +} + +=head3 $object->Set( $properties_hashref ) + +$object->Set( + { + property1 => $property1, + property2 => $property2, + property3 => $propery3, + } +); + +Enables multiple properties to be set at once + +Returns: + 1 if all properties were set. + 0 if one or more properties do not exist. + undef if all properties exist but a different error + prevents one or more properties from being set. + +If one or more of the properties do not exist, +no properties will be set. + +=cut + +sub Set { + my ( $self, $properties ) = @_; + + my @columns = @{$self->_Columns()}; + + foreach my $p ( keys %$properties ) { + unless ( $p ~~ @columns ) { + carp("No property $p!"); + return 0; + } + } + + return $self->_Result()->set_columns($properties) ? 1 : undef; +} + +=head3 $object->Id(); + +Returns the id of the object if it has one. + +=cut + +sub Id { + my ($self) = @_; + + my ( $id ) = $self->_Result()->id(); + + return $id; +} + +=head3 $object->_Result(); + +Returns the internal DBIC Row object + +=cut + +sub _Result { + my ($self) = @_; + + # If we don't have a dbic row at this point, we need to create an empty one + $self->{_result} ||= + Koha::Database->new()->schema()->resultset( $self->Type() )->new({}); + + return $self->{_result}; +} + +=head3 $object->_Columns(); + +Returns an arrayref of the table columns + +=cut + +sub _Columns { + my ($self) = @_; + + # If we don't have a dbic row at this point, we need to create an empty one + $self->{_columns} ||= [ $self->_Result()->result_source()->columns() ]; + + return $self->{_columns}; +} + + +=head3 AUTOLOAD + +The autoload method is used only to get and set values for an objects properties. + +=cut + +sub AUTOLOAD { + my $self = shift; + + my $method = our $AUTOLOAD; + $method =~ s/.*://; + + my @columns = @{$self->_Columns()}; + # Using direct setter/getter like $item->barcode() or $item->barcode($barcode); + if ( $method ~~ @columns ) { + if ( @_ ) { + return $self->_Result()->set_column( $method, @_ ); + } else { + my $value = $self->_Result()->get_column( $method ); + return encode( 'UTF-8', $value ); + } + } + + carp "No method $method!"; + return; +} + +=head3 Type + +This method must be defined in the child class. The value is the name of the DBIC resultset. +For example, for borrowers, the Type method will return "Borrower". + +=cut + +sub Type { } + +sub DESTROY { } + +=head1 AUTHOR + +Kyle M Hall + +=cut + +1; diff --git a/Koha/Objects.pm b/Koha/Objects.pm new file mode 100644 index 0000000..91d77c4 --- /dev/null +++ b/Koha/Objects.pm @@ -0,0 +1,163 @@ +package Koha::Objects; + +# Copyright ByWater Solutions 2014 +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use Carp; + +use Koha::Database; + +our $type; + +=head1 NAME + +Koha::Objects - Koha Object set base class + +=head1 SYNOPSIS + + use Koha::Objects; + my @objects = Koha::Objects->search({ borrowernumber => $borrowernumber}); + +=head1 DESCRIPTION + +This class must be subclassed. + +=head1 API + +=head2 Class Methods + +=cut + +=head3 Koha::Objects->new(); + +my $object = Koha::Object->new(); + +=cut + +sub new { + my ($class) = shift; + my $self = {}; + + bless( $self, $class ); +} + +=head3 Koha::Objects->Find(); + +my $object = Koha::Object->Find($id); +my $object = Koha::Object->Find( { keypart1 => $keypart1, keypart2 => $keypart2 } ); + +=cut + +sub Find { + my ( $self, $id ) = @_; + + my $result = $self->_ResultSet()->find($id); + + my $object = $self->ObjectClass()->new_from_dbic( $result ); + + return $object; +} + +=head3 Koha::Objects->Search(); + +my @objects = Koha::Object->Search($params); + +=cut + +sub Search { + my ( $self, $params ) = @_; + + my @dbic_rows = $self->_ResultSet()->search($params); + + my @objects = $self->_Wrap( @dbic_rows ); + + return wantarray ? @objects : \@objects; +} + +=head3 Koha::Objects->Count(); + +my @objects = Koha::Object->Count($params); + +=cut + +sub Count { + my ( $self, $params ) = @_; + + return $self->_ResultSet()->count($params); +} + +=head3 Koha::Objects->_Wrap + +Wraps the DBIC object in a corrosponding Koha object + +=cut + +sub _Wrap { + my ( $self, @dbic_rows ) = @_; + + my @objects = map { $self->ObjectClass()->new_from_dbic( $_ ) } @dbic_rows; + + return @objects; +} + +=head3 Koha::Objects->_ResultSet + +Returns the internal resultset or creates it if undefined + +=cut + +sub _ResultSet { + my ($self) = @_; + + $self->{_resultset} ||= + Koha::Database->new()->schema()->resultset( $self->Type() ); + + $self->{_resultset}; +} + +=head3 Type + +The type method must be set for all child classes. +The value returned by it should be the DBIC resultset name. +For example, for holds, Type should return 'Reserve'. + +=cut + +sub Type { } + +=head3 ObjectClass + +This method must be set for all child classes. +The value returned by it should be the name of the Koha +object class that is returned by this class. +For example, for holds, ObjectClass should return 'Koha::Hold'. + +=cut + +sub ObjectClass { } + +sub DESTROY { } + +=head1 AUTHOR + +Kyle M Hall + +=cut + +1; diff --git a/t/Borrower.t b/t/Borrower.t new file mode 100755 index 0000000..7a57cec --- /dev/null +++ b/t/Borrower.t @@ -0,0 +1,57 @@ +#!/usr/bin/perl + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Test::More tests => 13; +use Test::Warn; + +use Koha::Database; + +BEGIN { + use_ok('Koha::Object'); + use_ok('Koha::Borrower'); +} + +my $result = Koha::Database->new()->schema()->resultset('Borrower')->new({ surname => 'Test Borrower' }); +my $object = Koha::Borrower->new_from_dbic( $result ); + +is( $object->surname(), 'Test Borrower', "Accessor returns correct value" ); + +$object->surname('Test Borrower Surname'); +is( $object->surname(), 'Test Borrower Surname', "Accessor returns correct value after set" ); + +my $object2 = Koha::Borrower->new( { surname => 'Test Borrower 2' } ); +is( $object2->surname(), 'Test Borrower 2', "Accessor returns correct value" ); + +$object2->surname('Test Borrower Surname 2'); +is( $object2->surname(), 'Test Borrower Surname 2', "Accessor returns correct value after set" ); + +my $ret; +$ret = $object2->Set({ surname => "Test Borrower Surname 3", firstname => "Test Firstname" }); +is( $ret, 1, "Set returns 1 on success" ); +is( $object2->surname(), "Test Borrower Surname 3", "Set sets first field correctly" ); +is( $object2->firstname(), "Test Firstname", "Set sets second field correctly" ); + +$ret = $object->Set({ surname => "Test Borrower Surname 4", bork => "bork" }); +is( $object2->surname(), "Test Borrower Surname 3", "Bad Set does not set field" ); +is( $ret, 0, "Set returns 0 when passed a bad property" ); + +ok( ! defined $object->bork(), 'Bad getter returns undef' ); +ok( ! defined $object->bork('bork'), 'Bad setter returns undef' ); + +1; diff --git a/t/db_dependent/Borrower.t b/t/db_dependent/Borrower.t new file mode 100755 index 0000000..f982688 --- /dev/null +++ b/t/db_dependent/Borrower.t @@ -0,0 +1,70 @@ +#!/usr/bin/perl + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Test::More tests => 12; +use Test::Warn; + +use C4::Context; +use Koha::Database; + +BEGIN { + use_ok('Koha::Object'); + use_ok('Koha::Borrower'); +} + +# Start transaction +my $dbh = C4::Context->dbh; +$dbh->{AutoCommit} = 0; +$dbh->{RaiseError} = 1; + +my $categorycode = Koha::Database->new()->schema()->resultset('Category')->first()->categorycode(); +my $branchcode = Koha::Database->new()->schema()->resultset('Branch')->first()->branchcode(); + +my $object = Koha::Borrower->new(); + +is( $object->InStorage, 0, "Object is not in storage" ); + +$object->categorycode( $categorycode ); +$object->branchcode( $branchcode ); +$object->surname("Test Surname"); +$object->Store(); + +my $borrower = Koha::Database->new()->schema()->resultset('Borrower')->find( $object->borrowernumber() ); +is( $borrower->surname(), "Test Surname", "Object found in database" ); + +is( $object->InStorage, 1, "Object is now stored" ); + +is( $object->IsChanged(), 0, "Object is unchanged" ); +$object->surname("Test Surname 2"); +is( $object->IsChanged(), 1, "Object is changed" ); + +$object->Store(); +is( $object->IsChanged(), 0, "Object no longer marked as changed after being stored" ); + +$object->Set({ firstname => 'Test Firstname' }); +is( $object->IsChanged(), 1, "Object is changed after Set" ); +$object->Store(); +is( $object->IsChanged(), 0, "Object no longer marked as changed after being stored" ); + +$object->Delete(); +$borrower = Koha::Database->new()->schema()->resultset('Borrower')->find( $object->borrowernumber() ); +ok( ! $borrower, "Object no longer found in database" ); +is( $object->InStorage, 0, "Object is not in storage" ); + +1; diff --git a/t/db_dependent/Borrowers.t b/t/db_dependent/Borrowers.t new file mode 100755 index 0000000..24ecb0d --- /dev/null +++ b/t/db_dependent/Borrowers.t @@ -0,0 +1,77 @@ +#!/usr/bin/perl + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Test::More tests => 5; +use Test::Warn; + +use C4::Context; +use Koha::Database; + +BEGIN { + use_ok('Koha::Objects'); + use_ok('Koha::Borrowers'); +} + +# Start transaction +my $dbh = C4::Context->dbh; +$dbh->{AutoCommit} = 0; +$dbh->{RaiseError} = 1; +$dbh->do("DELETE FROM issues"); +$dbh->do("DELETE FROM borrowers"); + +my $categorycode = + Koha::Database->new()->schema()->resultset('Category')->first() + ->categorycode(); +my $branchcode = + Koha::Database->new()->schema()->resultset('Branch')->first()->branchcode(); + +my $b1 = Koha::Borrower->new( + { + surname => 'Test 1', + branchcode => $branchcode, + categorycode => $categorycode + } +); +$b1->Store(); +my $b2 = Koha::Borrower->new( + { + surname => 'Test 2', + branchcode => $branchcode, + categorycode => $categorycode + } +); +$b2->Store(); +my $b3 = Koha::Borrower->new( + { + surname => 'Test 3', + branchcode => $branchcode, + categorycode => $categorycode + } +); +$b3->Store(); + +my $b1_new = Koha::Borrowers->new()->Find( $b1->borrowernumber() ); +is( $b1->surname(), $b1_new->surname(), "Found matching borrower" ); + +my @borrowers = Koha::Borrowers->new()->Search( { branchcode => $branchcode } ); +is( @borrowers, 3, "Found 3 borrowers with Search" ); + +is( Koha::Borrowers->new()->Count( { branchcode => $branchcode } ), 3, "Counted 3 borrowers with Count" ); + +1; -- 1.7.2.5