1 package SL::DB::Object;
 
   6 use English qw(-no_match_vars);
 
   8 use Rose::DB::Object::Constants qw();
 
   9 use List::MoreUtils qw(any pairwise);
 
  12 use SL::DB::Helper::Attr;
 
  13 use SL::DB::Helper::Metadata;
 
  14 use SL::DB::Helper::Manager;
 
  15 use SL::DB::Helper::Presenter;
 
  16 use SL::DB::Object::Hooks;
 
  18 use base qw(Rose::DB::Object);
 
  20 my @rose_reserved_methods = qw(
 
  21   db dbh delete DESTROY error init_db _init_db insert load meta meta_class
 
  22   not_found save update import
 
  25 my %db_to_presenter_mapping = (
 
  26   Customer        => 'CustomerVendor',
 
  27   PurchaseInvoice => 'Invoice',
 
  28   Vendor          => 'CustomerVendor',
 
  29   GLTransaction   => 'GL',
 
  34   my $self  = $class->SUPER::new();
 
  36   $self->_assign_attributes(@_) if $self;
 
  42   my $class_or_self = shift;
 
  43   my $class         = ref($class_or_self) || $class_or_self;
 
  44   my $type          = $class =~ m/::Auth/ ? 'KIVITENDO_AUTH' : 'KIVITENDO';
 
  46   return SL::DB::create(undef, $type);
 
  50   return 'SL::DB::Helper::Metadata';
 
  53 sub _get_manager_class {
 
  54   my $class_or_self = shift;
 
  55   my $class         = ref($class_or_self) || $class_or_self;
 
  57   return $class->meta->convention_manager->auto_manager_class_name($class);
 
  60 my %text_column_types = (text => 1, char => 1, varchar => 1);
 
  62 sub assign_attributes {
 
  66   my $pk         = ref($self)->meta->primary_key;
 
  67   delete @attributes{$pk->column_names} if $pk;
 
  68   delete @attributes{@rose_reserved_methods};
 
  70   return $self->_assign_attributes(%attributes);
 
  73 sub _assign_attributes {
 
  77   my %types      = map { $_->name => $_->type } ref($self)->meta->columns;
 
  79   # Special case for *_as_man_days / *_as_man_days_string /
 
  80   # *_as_man_days_unit: the _unit variation must always be called
 
  81   # after the non-unit methods.
 
  82   my @man_days_attributes = grep { m/_as_man_days(?:_string)?$/ } keys %attributes;
 
  83   foreach my $attribute (@man_days_attributes) {
 
  84     my $value = delete $attributes{$attribute};
 
  85     $self->$attribute(defined($value) && ($value eq '') ? undef : $value);
 
  88   while (my ($attribute, $value) = each %attributes) {
 
  89     my $type = lc($types{$attribute} || 'text');
 
  90     $value   = $type eq 'boolean'                ? ($value ? 't' : 'f')
 
  91              : $text_column_types{$type}         ? $value
 
  92              : defined($value) && ($value eq '') ? undef
 
  94     $self->$attribute($value);
 
 100 sub update_attributes {
 
 103   $self->assign_attributes(@_)->save;
 
 111   return $self->$sub(@_);
 
 119   $check    = $check->($self) if ref($check) eq 'CODE';
 
 121   return $check ? $self->$sub(@_) : $self;
 
 124 sub get_first_conflicting {
 
 125   my ($self, @attributes) = @_;
 
 127   my $primary_key         = ($self->meta->primary_key)[0];
 
 128   my @where               = map { ($_ => $self->$_) } @attributes;
 
 130   push @where, ("!$primary_key" => $self->$primary_key) if $self->$primary_key;
 
 132   return $self->_get_manager_class->get_first(where => [ and => \@where ]);
 
 135 # These three functions cannot sit in SL::DB::Object::Hooks because
 
 136 # mixins don't deal well with super classes (SUPER is the current
 
 137 # package's super class, not $self's).
 
 139   my ($self, @args) = @_;
 
 141   SL::DB::Object::Hooks::run_hooks($self, 'before_load');
 
 142   my $result = $self->SUPER::load(@args);
 
 143   SL::DB::Object::Hooks::run_hooks($self, 'after_load', $result);
 
 149   my ($self, @args) = @_;
 
 153   $self->db->with_transaction(sub {
 
 154     SL::DB::Object::Hooks::run_hooks($self, 'before_save');
 
 155     $result = $self->SUPER::save(@args);
 
 156     SL::DB::Object::Hooks::run_hooks($self, 'after_save', $result);
 
 159   }) || die $self->db->error;
 
 165   my ($self, @args) = @_;
 
 169   $self->db->with_transaction(sub {
 
 170     SL::DB::Object::Hooks::run_hooks($self, 'before_delete');
 
 171     $result = $self->SUPER::delete(@args);
 
 172     SL::DB::Object::Hooks::run_hooks($self, 'after_delete', $result);
 
 175   }) || die $self->db->error;
 
 181   my $class_or_self = shift;
 
 183   my $class         = ref($class_or_self) || $class_or_self;
 
 184   my $cache         = $::request->cache("::SL::DB::Object::object_cache::${class}");
 
 186   croak "Missing ID" unless @ids;
 
 188   my @missing_ids = grep { !exists $cache->{$_} } @ids;
 
 190   return $cache->{$ids[0]} if !@missing_ids;
 
 192   croak "Caching can only be used with classes with exactly one primary key column" if 1 != scalar(@{ $class->meta->primary_key_columns });
 
 194   my $primary_key = $class->meta->primary_key_columns->[0]->name;
 
 195   my $objects     = $class->_get_manager_class->get_all(where => [ $primary_key => \@missing_ids ]);
 
 197   $cache->{$_->$primary_key} = $_ for @{ $objects};
 
 199   return $cache->{$ids[0]};
 
 202 sub invalidate_cached {
 
 203   my ($class_or_self, @ids) = @_;
 
 204   my $class                 = ref($class_or_self) || $class_or_self;
 
 206   if (ref($class_or_self) && !@ids) {
 
 207     croak "Caching can only be used with classes with exactly one primary key column" if 1 != scalar(@{ $class->meta->primary_key_columns });
 
 209     my $primary_key = $class->meta->primary_key_columns->[0]->name;
 
 210     @ids            = ($class_or_self->$primary_key);
 
 213   delete @{ $::request->cache("::SL::DB::Object::object_cache::${class}") }{ @ids };
 
 215   return $class_or_self;
 
 218 my %_skip_fields_when_cloning = map { ($_ => 1) } qw(itime mtime);
 
 220 sub clone_and_reset {
 
 222   my $class               = ref $self;
 
 223   my $cloning             = Rose::DB::Object::Constants::STATE_CLONING();
 
 224   local $self->{$cloning} = 1;
 
 226   my $meta                = $class->meta;
 
 227   my @accessors           = $meta->column_accessor_method_names;
 
 228   my @mutators            = $meta->column_mutator_method_names;
 
 230     grep     { $_->[0] && $_->[1] && !$_skip_fields_when_cloning{ $_->[0] } }
 
 231     pairwise { no warnings qw(once); [ $a, $b] } @accessors, @mutators;
 
 233   my $clone = $class->new(map { my $method = $_->[0]; ($_->[1] => $self->$method) } @column_names);
 
 235   # Blank all primary and unique key columns
 
 237     $meta->primary_key_column_mutator_names,
 
 238     map { my $uk = $_; map { $meta->column_mutator_method_name($_) } ($uk->columns) } ($meta->unique_keys)
 
 241   $clone->$_(undef) for @keys;
 
 243   # Also copy db object, if any
 
 244   $clone->db($self->{db}) if $self->{db};
 
 252   my $class =  ref $self;
 
 253   $class    =~ s{^SL::DB::}{};
 
 254   $class    =  "SL::Presenter::" . ($db_to_presenter_mapping{$class} // $class);
 
 256   return SL::DB::Helper::Presenter->new($class, $self);
 
 264       my $column_name = $_->name;
 
 265       my $value       = $self->$column_name;
 
 266       $value          = !defined($value) ? undef : "${value}";
 
 268     } $self->meta->columns
 
 282 SL::DB::Object: Base class for all of our model classes
 
 286 This is the base class from which all other model classes are
 
 287 derived. It contains functionality and settings required for all model
 
 290 Several functions (e.g. C<make_manager_class>, C<init_db>) in this
 
 291 class are used for setting up the classes / base classes used for all
 
 292 model instances. They overwrite the functions from
 
 299 =item assign_attributes %attributes
 
 301 =item _assign_attributes %attributes
 
 303 Assigns all elements from C<%attributes> to the columns by calling
 
 304 their setter functions. The difference between the two functions is
 
 305 that C<assign_attributes> protects primary key columns while
 
 306 C<_assign_attributes> doesn't.
 
 308 Both functions handle values that are empty strings by replacing them
 
 309 with C<undef> for non-text columns. This allows the calling functions
 
 310 to use data from HTML forms as the input for C<assign_attributes>
 
 311 without having to remove empty strings themselves (think of
 
 312 e.g. select boxes with an empty option which should be turned into
 
 313 C<NULL> in the database).
 
 315 =item update_attributes %attributes
 
 317 Assigns the attributes from C<%attributes> by calling the
 
 318 C<assign_attributes> function and saves the object afterwards. Returns
 
 321 =item _get_manager_class
 
 323 Returns the manager package for the object or class that it is called
 
 324 on. Can be used from methods in this package for getting the actual
 
 327 =item C<call_sub $name, @args>
 
 329 Calls the sub C<$name> on C<$self> with the arguments C<@args> and
 
 330 returns its result. This is meant for situations in which the sub's
 
 331 name is a composite, e.g.
 
 333   my $chart_id = $buchungsgruppe->call_sub(($is_sales ? "income" : "expense") . "_accno_id_${taxzone_id}");
 
 335 =item C<call_sub_if $name, $check, @args>
 
 337 Calls the sub C<$name> on C<$self> with the arguments C<@args> if
 
 338 C<$check> is trueish. If C<$check> is a code reference then it will be
 
 339 called with C<$self> as the only argument and its result determines
 
 340 whether or not C<$name> is called.
 
 342 Returns the sub's result if the check is positive and C<$self>
 
 345 =item C<get_first_conflicting @attributes>
 
 347 Returns the first object for which all properties listed in
 
 348 C<@attributes> equal those in C<$self> but which is not C<$self>. Can
 
 349 be used to check whether or not an object's columns are unique before
 
 350 saving or during validation.
 
 352 =item C<load_cached @ids>
 
 354 Loads objects from the database which haven't been cached before and
 
 355 caches them for the duration of the current request (see
 
 356 L<SL::Request/cache>).
 
 358 If you know in advance that you will likely need all objects of a
 
 359 particular type then you can pre-cache them by calling the manager's
 
 360 C<cache_all> function. For example, if you expect to need all unit
 
 361 objects, you can use C<SL::DB::Manager::Unit-E<gt>cache_all> before
 
 362 you start the actual work. Later you can use
 
 363 C<SL::DB::Unit-E<gt>load_cached> to retrieve individual objects and be
 
 364 sure that they're already cached.
 
 366 This method can be called both as an instance method and a class
 
 367 method. It loads objects for the corresponding class (e.g. both
 
 368 C<SL::DB::Part-E<gt>load_cached(…)> and
 
 369 C<$some_part-E<gt>load_cached(…)> will load parts).
 
 371 Currently only classes with a single primary key column are supported.
 
 373 Returns the cached object for the first ID.
 
 375 =item C<invalidate_cached @ids>
 
 377 Deletes all cached instances of this class (see L</load_cached>) for
 
 380 If called as an instance method without further arguments then the
 
 383 Returns the object/class it was called on.
 
 385 =item C<clone_and_reset>
 
 387 This works similar to L<Rose::DB::Object::Helpers/clone_and_reset>: it
 
 388 returns a cloned instance of C<$self>. All primary and unique key
 
 389 fields have been reset.
 
 391 The difference between Rose's and this function is that this function
 
 392 will also skip setting the following fields if such columns exist for
 
 393 C<$self>: C<itime>, C<mtime>.
 
 397 Returns a proxy wrapper that will dispatch all method calls to the presenter
 
 398 with the same name as the class of the involking object.
 
 400 For the full documentation about its capabilites see
 
 401 L<SL::DB::Helper::Presenter>
 
 403 =item C<as_debug_info>
 
 405 Returns a hash containing solely the essentials for dumping it with
 
 406 L<LXDebug/dump>. The returned hash consists of the column names with
 
 407 associated column values in stringified form.
 
 413 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>