use Carp;
use Data::Dumper;
use List::Util qw(first);
+use List::UtilsBy qw(partition_by);
use constant META_CVARS => 'cvars_config';
my $caller_package = caller;
# TODO: if module is empty, module overloading needs to take effect
- # certain stuff may have more than one overload, odr even more than one type
+ # certain stuff may have more than one overload, or even more than one type
defined $caller_package or croak 'need to be included from a caller reference';
$params{module} ||= _calc_modules_from_overloads(%params) if $params{overloads};
my $configs = _all_configs(%params);
my $cvars = $self->custom_variables;
my %cvars_by_config = map { $_->config_id => $_ } @$cvars;
+ my $invalids = _all_invalids($self->${\ $self->meta->primary_key_columns->[0]->name }, $configs, %params);
+ my %invalids_by_config = map { $_->config_id => 1 } @$invalids;
my @return = map(
{
+ my $cvar;
if ( $cvars_by_config{$_->id} ) {
- $cvars_by_config{$_->id};
+ $cvar = $cvars_by_config{$_->id};
}
else {
- my $cvar = _new_cvar($self, %params, config => $_);
+ $cvar = _new_cvar($self, %params, config => $_);
$self->add_custom_variables($cvar);
- $cvar;
}
+ $cvar->{is_valid} = !$invalids_by_config{$_->id};
+ $cvar->{config} = $_;
+ $cvar;
}
@$configs
);
require SL::DB::CustomVariableConfig;
- SL::DB::Manager::CustomVariableConfig->get_all_sorted($params{module} ? (query => [ module => $params{module} ]) : ());
+ my $cache = $::request->cache("::SL::DB::Helper::CustomVariables::object_cache");
+
+ if (!$cache->{all}) {
+ my $configs = SL::DB::Manager::CustomVariableConfig->get_all_sorted;
+ $cache->{all} = $configs;
+ $cache->{module} = { partition_by { $_->module } @$configs };
+ }
+
+ return $params{module} && !ref $params{module} ? $cache->{module}{$params{module}}
+ : $params{module} && ref $params{module} ? [ map { @{ $cache->{module}{$_} // [] } } @{ $params{module} } ]
+ : $cache->{all};
}
sub _overload_by_module {
die "invalid config_id in $caller_package\::cvar custom filter: expected module $params{module} - got @{[ $config->module ]}";
}
+ my @filter;
+ if ($config->type eq 'bool') {
+ @filter = $value ? ($config->value_col => 1) : (or => [ $config->value_col => undef, $config->value_col => 0 ]);
+ } else {
+ @filter = ($config->value_col => $value);
+ }
+
my (%query, %bind_vals);
($query{customized}, $bind_vals{customized}) = Rose::DB::Object::QueryBuilder::build_select(
dbh => $config->dbh,
query => [
config_id => $config_id,
sub_module => $params{sub_module},
- $config->value_col => $value,
+ @filter,
],
query_is_sql => 1,
);
+ if ($config->type eq 'bool') {
+ if ($value) {
+ @filter = (
+ '!default_value' => undef,
+ '!default_value' => '',
+ default_value => '1',
+ );
+
+ } else {
+ @filter = (
+ or => [
+ default_value => '0',
+ default_value => '',
+ default_value => undef,
+ ],
+ );
+ }
+
+ } else {
+ @filter = (
+ '!default_value' => undef,
+ '!default_value' => '',
+ default_value => $value,
+ );
+ }
+
+
my $conversion = $config->type =~ m{^(?:date|timestamp)$} ? $config->type
: $config->type =~ m{^(?:customer|vendor|part)$} ? 'integer'
- : $config->type eq 'bool' ? 'boolean'
: $config->type eq 'number' ? 'numeric'
: '';
($query{config}, $bind_vals{config}) = Rose::DB::Object::QueryBuilder::build_select(
- dbh => $config->dbh,
- select => 'id',
- tables => [ 'custom_variable_configs' ],
- columns => { custom_variable_configs => [ qw(id default_value) ] },
- query => [
- id => $config->id,
- default_value => $value,
+ dbh => $config->dbh,
+ select => 'id',
+ tables => [ 'custom_variable_configs' ],
+ columns => { custom_variable_configs => [ qw(id default_value) ] },
+ query => [
+ id => $config->id,
+ @filter,
],
- query_is_sql => 1,
+ query_is_sql => 1,
);
- $query{config} =~ s{\bdefault_value\b}{default_value::${conversion}} if $conversion;
+ $query{config} =~ s{ (?<! NOT\( ) default_value (?! \s*is\s+not\s+null) }{default_value::${conversion}}x if $conversion;
($query{not_customized}, $bind_vals{not_customized}) = Rose::DB::Object::QueryBuilder::build_select(
dbh => $config->dbh,
# remove rose aliases. query builder sadly is not reentrant, and will reuse the same aliases. :(
$query{$key} =~ s{\bt\d+(?:\.)?\b}{}g;
- # manually inline the values. again, rose doen't know how to handly bind params in subqueries :(
- $query{$key} =~ s{\?}{ $config->dbh->quote($_) }xe for @{ $bind_vals{$key} };
+ # manually inline the values. again, rose doesn't know how to handle bind params in subqueries :(
+ $query{$key} =~ s{\?}{ $config->dbh->quote(shift @{ $bind_vals{$key} }) }xeg;
$query{$key} =~ s{\n}{ }g;
}
);
}
+
+sub _all_invalids {
+ my ($trans_id, $configs, %params) = @_;
+
+ require SL::DB::CustomVariableValidity;
+
+ # easy 1: no trans_id, all valid by default.
+ return [] unless $trans_id;
+
+ # easy 2: no module in params? no validity
+ return [] unless $params{module};
+
+ my %wanted_modules = ref $params{module} ? map { $_ => 1 } @{ $params{module} } : ($params{module} => 1);
+ my @module_configs = grep { $wanted_modules{$_->module} } @$configs;
+
+ return [] unless @module_configs;
+
+ # nor find all entries for that and return
+ SL::DB::Manager::CustomVariableValidity->get_all(
+ query => [
+ config_id => [ map { $_->id } @module_configs ],
+ trans_id => $trans_id,
+ ]
+ );
+}
+
1;
__END__
=head1 NAME
-SL::DB::Helper::CustomVariables - Mixin to provide custom variables relations
+SL::DB::Helper::CustomVariables - Mixin to provide custom variable relations
=head1 SYNOPSIS
=item C<cvars_by_config>
-Thi will return a list of CVars with the following changes over the standard accessor:
+This will return a list of CVars with the following changes over the standard accessor:
=over 4
=back
+=head1 BUGS AND CAVEATS
+
+=over 4
+
+=item * Conditional method export
+
+Prolonged use has shown that users expect all methods to be present or none.
+Future versions of this will likely remove the optional aliasing.
+
+=item * Semantics need to be updated
+
+There are a few transitions that are currently neither supported nor well
+defined, most of them happening when the config of a cvar gets changed, but
+whose instances have already been saved. This needs to be cleaned up.
+
+=back
+
=head1 AUTHOR
Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>,