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);
10 use List::Util qw(first);
13 use SL::DB::Helper::Attr;
14 use SL::DB::Helper::Metadata;
15 use SL::DB::Helper::Manager;
16 use SL::DB::Helper::Presenter;
17 use SL::DB::Object::Hooks;
19 use base qw(Rose::DB::Object);
21 my @rose_reserved_methods = qw(
22 db dbh delete DESTROY error init_db _init_db insert load meta meta_class
23 not_found save update import
26 my %db_to_presenter_mapping = (
27 Customer => 'CustomerVendor',
28 PurchaseInvoice => 'Invoice',
29 Vendor => 'CustomerVendor',
30 GLTransaction => 'GL',
35 my $self = $class->SUPER::new();
37 $self->_assign_attributes(@_) if $self;
43 my $class_or_self = shift;
44 my $class = ref($class_or_self) || $class_or_self;
45 my $type = $class =~ m/::Auth/ ? 'KIVITENDO_AUTH' : 'KIVITENDO';
47 return SL::DB::create(undef, $type);
51 return 'SL::DB::Helper::Metadata';
54 sub _get_manager_class {
55 my $class_or_self = shift;
56 my $class = ref($class_or_self) || $class_or_self;
58 return $class->meta->convention_manager->auto_manager_class_name($class);
61 my %text_column_types = (text => 1, char => 1, varchar => 1);
63 sub assign_attributes {
67 my $pk = ref($self)->meta->primary_key;
68 delete @attributes{$pk->column_names} if $pk;
69 delete @attributes{@rose_reserved_methods};
71 return $self->_assign_attributes(%attributes);
74 sub _assign_attributes {
78 my %types = map { $_->name => $_->type } ref($self)->meta->columns;
80 # Special case for *_as_man_days / *_as_man_days_string /
81 # *_as_man_days_unit: the _unit variation must always be called
82 # after the non-unit methods.
83 my @man_days_attributes = grep { m/_as_man_days(?:_string)?$/ } keys %attributes;
84 foreach my $attribute (@man_days_attributes) {
85 my $value = delete $attributes{$attribute};
86 $self->$attribute(defined($value) && ($value eq '') ? undef : $value);
89 while (my ($attribute, $value) = each %attributes) {
90 my $type = lc($types{$attribute} || 'text');
91 $value = $type eq 'boolean' ? ($value ? 't' : 'f')
92 : $text_column_types{$type} ? $value
93 : defined($value) && ($value eq '') ? undef
95 $self->$attribute($value);
101 sub update_attributes {
104 $self->assign_attributes(@_)->save;
109 sub update_collection {
110 my ($self, $attribute, $entries) = @_;
112 my $self_primary_key = "" . ($self->meta->primary_key_columns)[0];
114 croak "\$self hasn't been saved yet" if !$self->$self_primary_key;
116 my $relationship = first { $_->name eq $attribute } @{ $self->meta->relationships };
118 croak "No relationship found for attribute '$attribute'" if !$relationship;
120 my @primary_key_columns = $relationship->class->meta->primary_key_columns;
122 croak "Classes with multiple primary key columns are not supported" if scalar(@primary_key_columns) > 1;
124 my $class = $relationship->class;
125 my $manager_class = "SL::DB::Manager::" . substr($class, 8);
126 my $other_primary_key = "" . $primary_key_columns[0];
127 my $column_map = $relationship->column_map;
128 my @new_entries = @{ $entries // [] };
129 my @existing_entries = @{ $self->$attribute // [] };
130 my @to_delete = grep { my $value = $_->$other_primary_key; !any { $_->{$other_primary_key} == $value } @new_entries } @existing_entries;
132 $_->delete for @to_delete;
134 foreach my $entry (@new_entries) {
135 if (!$entry->{$other_primary_key}) {
136 my $new_instance = $class->new(%{ $entry });
138 foreach my $self_attribute (keys %{ $column_map }) {
139 my $other_attribute = $column_map->{$self_attribute};
140 $new_instance->$other_attribute($self->$self_attribute);
148 my $existing = first { $_->$other_primary_key == $entry->{$other_primary_key} } @existing_entries;
149 $existing->update_attributes(%{ $entry }) if $existing;
156 return $self->$sub(@_);
164 $check = $check->($self) if ref($check) eq 'CODE';
166 return $check ? $self->$sub(@_) : $self;
169 sub get_first_conflicting {
170 my ($self, @attributes) = @_;
172 my $primary_key = ($self->meta->primary_key)[0];
173 my @where = map { ($_ => $self->$_) } @attributes;
175 push @where, ("!$primary_key" => $self->$primary_key) if $self->$primary_key;
177 return $self->_get_manager_class->get_first(where => [ and => \@where ]);
180 # These three functions cannot sit in SL::DB::Object::Hooks because
181 # mixins don't deal well with super classes (SUPER is the current
182 # package's super class, not $self's).
184 my ($self, @args) = @_;
186 SL::DB::Object::Hooks::run_hooks($self, 'before_load');
187 my $result = $self->SUPER::load(@args);
188 SL::DB::Object::Hooks::run_hooks($self, 'after_load', $result);
194 my ($self, @args) = @_;
198 $self->db->with_transaction(sub {
199 SL::DB::Object::Hooks::run_hooks($self, 'before_save');
200 $result = $self->SUPER::save(@args);
201 SL::DB::Object::Hooks::run_hooks($self, 'after_save', $result);
204 }) || die $self->db->error;
210 my ($self, @args) = @_;
214 $self->db->with_transaction(sub {
215 SL::DB::Object::Hooks::run_hooks($self, 'before_delete');
216 $result = $self->SUPER::delete(@args);
217 SL::DB::Object::Hooks::run_hooks($self, 'after_delete', $result);
220 }) || die $self->db->error;
226 my $class_or_self = shift;
228 my $class = ref($class_or_self) || $class_or_self;
229 my $cache = $::request->cache("::SL::DB::Object::object_cache::${class}");
231 croak "Missing ID" unless @ids;
233 my @missing_ids = grep { !exists $cache->{$_} } @ids;
235 return $cache->{$ids[0]} if !@missing_ids;
237 croak "Caching can only be used with classes with exactly one primary key column" if 1 != scalar(@{ $class->meta->primary_key_columns });
239 my $primary_key = $class->meta->primary_key_columns->[0]->name;
240 my $objects = $class->_get_manager_class->get_all(where => [ $primary_key => \@missing_ids ]);
242 $cache->{$_->$primary_key} = $_ for @{ $objects};
244 return $cache->{$ids[0]};
247 sub invalidate_cached {
248 my ($class_or_self, @ids) = @_;
249 my $class = ref($class_or_self) || $class_or_self;
251 if (ref($class_or_self) && !@ids) {
252 croak "Caching can only be used with classes with exactly one primary key column" if 1 != scalar(@{ $class->meta->primary_key_columns });
254 my $primary_key = $class->meta->primary_key_columns->[0]->name;
255 @ids = ($class_or_self->$primary_key);
258 delete @{ $::request->cache("::SL::DB::Object::object_cache::${class}") }{ @ids };
260 return $class_or_self;
263 my %_skip_fields_when_cloning = map { ($_ => 1) } qw(itime mtime);
265 sub clone_and_reset {
267 my $class = ref $self;
268 my $cloning = Rose::DB::Object::Constants::STATE_CLONING();
269 local $self->{$cloning} = 1;
271 my $meta = $class->meta;
272 my @accessors = $meta->column_accessor_method_names;
273 my @mutators = $meta->column_mutator_method_names;
275 grep { $_->[0] && $_->[1] && !$_skip_fields_when_cloning{ $_->[0] } }
276 pairwise { no warnings qw(once); [ $a, $b] } @accessors, @mutators;
278 my $clone = $class->new(map { my $method = $_->[0]; ($_->[1] => $self->$method) } @column_names);
280 # Blank all primary and unique key columns
282 $meta->primary_key_column_mutator_names,
283 map { my $uk = $_; map { $meta->column_mutator_method_name($_) } ($uk->columns) } ($meta->unique_keys)
286 $clone->$_(undef) for @keys;
288 # Also copy db object, if any
289 $clone->db($self->{db}) if $self->{db};
297 my $class = ref $self;
298 $class =~ s{^SL::DB::}{};
299 $class = "SL::Presenter::" . ($db_to_presenter_mapping{$class} // $class);
301 return SL::DB::Helper::Presenter->new($class, $self);
309 my $column_name = $_->name;
310 my $value = $self->$column_name;
311 $value = !defined($value) ? undef : "${value}";
313 } $self->meta->columns
327 SL::DB::Object: Base class for all of our model classes
331 This is the base class from which all other model classes are
332 derived. It contains functionality and settings required for all model
335 Several functions (e.g. C<make_manager_class>, C<init_db>) in this
336 class are used for setting up the classes / base classes used for all
337 model instances. They overwrite the functions from
344 =item assign_attributes %attributes
346 =item _assign_attributes %attributes
348 Assigns all elements from C<%attributes> to the columns by calling
349 their setter functions. The difference between the two functions is
350 that C<assign_attributes> protects primary key columns while
351 C<_assign_attributes> doesn't.
353 Both functions handle values that are empty strings by replacing them
354 with C<undef> for non-text columns. This allows the calling functions
355 to use data from HTML forms as the input for C<assign_attributes>
356 without having to remove empty strings themselves (think of
357 e.g. select boxes with an empty option which should be turned into
358 C<NULL> in the database).
360 =item update_attributes %attributes
362 Assigns the attributes from C<%attributes> by calling the
363 C<assign_attributes> function and saves the object afterwards. Returns
366 =item C<update_collection $attribute, $entries, %params>
368 Updates a one-to-many relationship named C<$attribute> to match the
369 entries in C<$entries>. C<$entries> is supposed to be an array ref of
372 For each hash ref in C<$entries> that does not contain a field for the
373 relationship's primary key column, this function creates a new entry
374 in the database with its attributes set to the data in the entry.
376 For each hash ref in C<$entries> that contains a field for the
377 relationship's primary key column, this function looks up the
378 corresponding entry in C<$self->$attribute> & updates its
379 attributes with the data in the entry.
381 All objects in C<$self->$attribute> for which no corresponding
382 entry exists in C<$entries> are deleted by calling the object's
385 In all cases the relationship itself C<$self->$attribute> is not
388 =item _get_manager_class
390 Returns the manager package for the object or class that it is called
391 on. Can be used from methods in this package for getting the actual
394 =item C<call_sub $name, @args>
396 Calls the sub C<$name> on C<$self> with the arguments C<@args> and
397 returns its result. This is meant for situations in which the sub's
398 name is a composite, e.g.
400 my $chart_id = $buchungsgruppe->call_sub(($is_sales ? "income" : "expense") . "_accno_id_${taxzone_id}");
402 =item C<call_sub_if $name, $check, @args>
404 Calls the sub C<$name> on C<$self> with the arguments C<@args> if
405 C<$check> is trueish. If C<$check> is a code reference then it will be
406 called with C<$self> as the only argument and its result determines
407 whether or not C<$name> is called.
409 Returns the sub's result if the check is positive and C<$self>
412 =item C<get_first_conflicting @attributes>
414 Returns the first object for which all properties listed in
415 C<@attributes> equal those in C<$self> but which is not C<$self>. Can
416 be used to check whether or not an object's columns are unique before
417 saving or during validation.
419 =item C<load_cached @ids>
421 Loads objects from the database which haven't been cached before and
422 caches them for the duration of the current request (see
423 L<SL::Request/cache>).
425 If you know in advance that you will likely need all objects of a
426 particular type then you can pre-cache them by calling the manager's
427 C<cache_all> function. For example, if you expect to need all unit
428 objects, you can use C<SL::DB::Manager::Unit-E<gt>cache_all> before
429 you start the actual work. Later you can use
430 C<SL::DB::Unit-E<gt>load_cached> to retrieve individual objects and be
431 sure that they're already cached.
433 This method can be called both as an instance method and a class
434 method. It loads objects for the corresponding class (e.g. both
435 C<SL::DB::Part-E<gt>load_cached(…)> and
436 C<$some_part-E<gt>load_cached(…)> will load parts).
438 Currently only classes with a single primary key column are supported.
440 Returns the cached object for the first ID.
442 =item C<invalidate_cached @ids>
444 Deletes all cached instances of this class (see L</load_cached>) for
447 If called as an instance method without further arguments then the
450 Returns the object/class it was called on.
452 =item C<clone_and_reset>
454 This works similar to L<Rose::DB::Object::Helpers/clone_and_reset>: it
455 returns a cloned instance of C<$self>. All primary and unique key
456 fields have been reset.
458 The difference between Rose's and this function is that this function
459 will also skip setting the following fields if such columns exist for
460 C<$self>: C<itime>, C<mtime>.
464 Returns a proxy wrapper that will dispatch all method calls to the presenter
465 with the same name as the class of the involking object.
467 For the full documentation about its capabilites see
468 L<SL::DB::Helper::Presenter>
470 =item C<as_debug_info>
472 Returns a hash containing solely the essentials for dumping it with
473 L<LXDebug/dump>. The returned hash consists of the column names with
474 associated column values in stringified form.
480 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>