Projekt

Allgemein

Profil

Herunterladen (8,85 KB) Statistiken
| Zweig: | Markierung: | Revision:
82515b2d Sven Schöling
package SL::DB::Object;

use strict;

db0ab48c Moritz Bunkus
use Carp;
17e54707 Moritz Bunkus
use English qw(-no_match_vars);
82515b2d Sven Schöling
use Rose::DB::Object;
use List::MoreUtils qw(any);

use SL::DB;
ac552280 Moritz Bunkus
use SL::DB::Helper::Attr;
use SL::DB::Helper::Metadata;
use SL::DB::Helper::Manager;
07d690e4 Moritz Bunkus
use SL::DB::Object::Hooks;
82515b2d Sven Schöling
use base qw(Rose::DB::Object);

7da9d5b6 Sven Schöling
my @rose_reserved_methods = qw(
db dbh delete DESTROY error init_db _init_db insert load meta meta_class
11dd30f1 Sven Schöling
not_found save update import
7da9d5b6 Sven Schöling
);

82515b2d Sven Schöling
sub new {
my $class = shift;
my $self = $class->SUPER::new();

$self->_assign_attributes(@_) if $self;

return $self;
}

sub init_db {
my $class_or_self = shift;
my $class = ref($class_or_self) || $class_or_self;
0674bc84 Moritz Bunkus
my $type = $class =~ m/::Auth/ ? 'KIVITENDO_AUTH' : 'KIVITENDO';
82515b2d Sven Schöling
return SL::DB::create(undef, $type);
}

sub meta_class {
ac552280 Moritz Bunkus
return 'SL::DB::Helper::Metadata';
82515b2d Sven Schöling
}

sub _get_manager_class {
my $class_or_self = shift;
my $class = ref($class_or_self) || $class_or_self;

return $class->meta->convention_manager->auto_manager_class_name($class);
}

dc04e2cb Moritz Bunkus
my %text_column_types = (text => 1, char => 1, varchar => 1);
82515b2d Sven Schöling
sub assign_attributes {
my $self = shift;
my %attributes = @_;

my $pk = ref($self)->meta->primary_key;
delete @attributes{$pk->column_names} if $pk;
7da9d5b6 Sven Schöling
delete @attributes{@rose_reserved_methods};
82515b2d Sven Schöling
return $self->_assign_attributes(%attributes);
}

sub _assign_attributes {
my $self = shift;
my %attributes = @_;

my %types = map { $_->name => $_->type } ref($self)->meta->columns;

6395006f Moritz Bunkus
# Special case for *_as_man_days / *_as_man_days_string /
# *_as_man_days_unit: the _unit variation must always be called
# after the non-unit methods.
my @man_days_attributes = grep { m/_as_man_days(?:_string)?$/ } keys %attributes;
4180aaea Moritz Bunkus
foreach my $attribute (@man_days_attributes) {
my $value = delete $attributes{$attribute};
$self->$attribute(defined($value) && ($value eq '') ? undef : $value);
}

82515b2d Sven Schöling
while (my ($attribute, $value) = each %attributes) {
my $type = lc($types{$attribute} || 'text');
321730c8 Moritz Bunkus
$value = $type eq 'boolean' ? ($value ? 't' : 'f')
: $text_column_types{$type} ? $value
: defined($value) && ($value eq '') ? undef
: $value;
82515b2d Sven Schöling
$self->$attribute($value);
}

return $self;
}

sub update_attributes {
my $self = shift;

$self->assign_attributes(@_)->save;

return $self;
}

6fd01c3d Moritz Bunkus
sub call_sub {
my $self = shift;
my $sub = shift;
return $self->$sub(@_);
}

c73b2658 Moritz Bunkus
sub call_sub_if {
my $self = shift;
my $sub = shift;
my $check = shift;

$check = $check->($self) if ref($check) eq 'CODE';

return $check ? $self->$sub(@_) : $self;
}

0b89d2ca Moritz Bunkus
sub get_first_conflicting {
my ($self, @attributes) = @_;

my $primary_key = ($self->meta->primary_key)[0];
my @where = map { ($_ => $self->$_) } @attributes;

push @where, ("!$primary_key" => $self->$primary_key) if $self->$primary_key;

return $self->_get_manager_class->get_first(where => [ and => \@where ]);
}

07d690e4 Moritz Bunkus
# These three functions cannot sit in SL::DB::Object::Hooks because
# mixins don't deal well with super classes (SUPER is the current
# package's super class, not $self's).
sub load {
my ($self, @args) = @_;

SL::DB::Object::Hooks::run_hooks($self, 'before_load');
my $result = $self->SUPER::load(@args);
SL::DB::Object::Hooks::run_hooks($self, 'after_load', $result);

return $result;
}

sub save {
my ($self, @args) = @_;
d49485e0 Moritz Bunkus
17e54707 Moritz Bunkus
my ($result, $exception);
d49485e0 Moritz Bunkus
my $worker = sub {
17e54707 Moritz Bunkus
$exception = $EVAL_ERROR unless eval {
0bae0979 Moritz Bunkus
SL::DB::Object::Hooks::run_hooks($self, 'before_save');
17e54707 Moritz Bunkus
$result = $self->SUPER::save(@args);
0bae0979 Moritz Bunkus
SL::DB::Object::Hooks::run_hooks($self, 'after_save', $result);
17e54707 Moritz Bunkus
1;
};
d16fc161 Sven Schöling
return $result;
07d690e4 Moritz Bunkus
};

d49485e0 Moritz Bunkus
$self->db->in_transaction ? $worker->() : $self->db->do_transaction($worker);
17e54707 Moritz Bunkus
die $exception if $exception;

d49485e0 Moritz Bunkus
return $result;
07d690e4 Moritz Bunkus
}

sub delete {
my ($self, @args) = @_;
d49485e0 Moritz Bunkus
17e54707 Moritz Bunkus
my ($result, $exception);
d49485e0 Moritz Bunkus
my $worker = sub {
17e54707 Moritz Bunkus
$exception = $EVAL_ERROR unless eval {
0bae0979 Moritz Bunkus
SL::DB::Object::Hooks::run_hooks($self, 'before_delete');
17e54707 Moritz Bunkus
$result = $self->SUPER::delete(@args);
0bae0979 Moritz Bunkus
SL::DB::Object::Hooks::run_hooks($self, 'after_delete', $result);
17e54707 Moritz Bunkus
1;
};
d16fc161 Sven Schöling
return $result;
07d690e4 Moritz Bunkus
};

d49485e0 Moritz Bunkus
$self->db->in_transaction ? $worker->() : $self->db->do_transaction($worker);
17e54707 Moritz Bunkus
die $exception if $exception;

d49485e0 Moritz Bunkus
return $result;
07d690e4 Moritz Bunkus
}

db0ab48c Moritz Bunkus
sub load_cached {
my $class_or_self = shift;
my @ids = @_;
my $class = ref($class_or_self) || $class_or_self;
my $cache = $::request->cache("::SL::DB::Object::object_cache::${class}");

croak "Missing ID" unless @ids;

my @missing_ids = grep { !exists $cache->{$_} } @ids;

return $cache->{$ids[0]} if !@missing_ids;

croak "Caching can only be used with classes with exactly one primary key column" if 1 != scalar(@{ $class->meta->primary_key_columns });

my $primary_key = $class->meta->primary_key_columns->[0]->name;
my $objects = $class->_get_manager_class->get_all(where => [ $primary_key => \@missing_ids ]);

$cache->{$_->$primary_key} = $_ for @{ $objects};

return $cache->{$ids[0]};
}

sub invalidate_cached {
my ($class_or_self, @ids) = @_;
my $class = ref($class_or_self) || $class_or_self;

if (ref($class_or_self) && !@ids) {
croak "Caching can only be used with classes with exactly one primary key column" if 1 != scalar(@{ $class->meta->primary_key_columns });

my $primary_key = $class->meta->primary_key_columns->[0]->name;
@ids = ($class_or_self->$primary_key);
}

delete @{ $::request->cache("::SL::DB::Object::object_cache::${class}") }{ @ids };

return $class_or_self;
}

82515b2d Sven Schöling
1;

__END__

=pod

db0ab48c Moritz Bunkus
=encoding utf8

82515b2d Sven Schöling
=head1 NAME

SL::DB::Object: Base class for all of our model classes

=head1 DESCRIPTION

This is the base class from which all other model classes are
derived. It contains functionality and settings required for all model
classes.

Several functions (e.g. C<make_manager_class>, C<init_db>) in this
class are used for setting up the classes / base classes used for all
model instances. They overwrite the functions from
L<Rose::DB::Object>.

=head1 FUNCTIONS

=over 4

=item assign_attributes %attributes

=item _assign_attributes %attributes

Assigns all elements from C<%attributes> to the columns by calling
their setter functions. The difference between the two functions is
that C<assign_attributes> protects primary key columns while
C<_assign_attributes> doesn't.

Both functions handle values that are empty strings by replacing them
with C<undef> for non-text columns. This allows the calling functions
to use data from HTML forms as the input for C<assign_attributes>
without having to remove empty strings themselves (think of
e.g. select boxes with an empty option which should be turned into
C<NULL> in the database).

=item update_attributes %attributes

Assigns the attributes from C<%attributes> by calling the
C<assign_attributes> function and saves the object afterwards. Returns
the object itself.

=item _get_manager_class

Returns the manager package for the object or class that it is called
on. Can be used from methods in this package for getting the actual
object's manager.

6fd01c3d Moritz Bunkus
=item C<call_sub $name, @args>

Calls the sub C<$name> on C<$self> with the arguments C<@args> and
returns its result. This is meant for situations in which the sub's
name is a composite, e.g.

my $chart_id = $buchungsgruppe->call_sub(($is_sales ? "income" : "expense") . "_accno_id_${taxzone_id}");

c73b2658 Moritz Bunkus
=item C<call_sub_if $name, $check, @args>

Calls the sub C<$name> on C<$self> with the arguments C<@args> if
C<$check> is trueish. If C<$check> is a code reference then it will be
called with C<$self> as the only argument and its result determines
whether or not C<$name> is called.

Returns the sub's result if the check is positive and C<$self>
otherwise.

0b89d2ca Moritz Bunkus
=item C<get_first_conflicting @attributes>

Returns the first object for which all properties listed in
C<@attributes> equal those in C<$self> but which is not C<$self>. Can
be used to check whether or not an object's columns are unique before
saving or during validation.

db0ab48c Moritz Bunkus
=item C<load_cached @ids>

Loads objects from the database which haven't been cached before and
caches them for the duration of the current request (see
L<SL::Request/cache>).

This method can be called both as an instance method and a class
method. It loads objects for the corresponding class (e.g. both
C<SL::DB::Part-E<gt>load_cached(…)> and
C<$some_part-E<gt>load_cached(…)> will load parts).

Currently only classes with a single primary key column are supported.

Returns the cached object for the first ID.

=item C<invalidate_cached @ids>

Deletes all cached instances of this class (see L</load_cached>) for
the given IDs.

If called as an instance method without further arguments then the
object's ID is used.

Returns the object/class it was called on.

82515b2d Sven Schöling
=back

=head1 AUTHOR

Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>

=cut