diff --git a/lib/QBit/Application/Model/DBManager.pm b/lib/QBit/Application/Model/DBManager.pm index ba2b85d..90d0b85 100644 --- a/lib/QBit/Application/Model/DBManager.pm +++ b/lib/QBit/Application/Model/DBManager.pm @@ -146,6 +146,10 @@ sub get_all { $self->{'__FOUND_ROWS__'} = $query->found_rows() if $opts{'calc_rows'}; if (@$result) { + $self->timelog->start(gettext('Preprocess easy fields')); + $self->pre_process_easy_fields($fields, $result); + $self->timelog->finish(); + $self->timelog->start(gettext('Preprocess fields')); $self->pre_process_fields($fields, $result); $self->timelog->finish(); @@ -197,6 +201,92 @@ sub get_db_filter { sub pre_process_fields { } +sub pre_process_easy_fields { + my ($self, $obj_fields, $data) = @_; + + my $fields = $obj_fields->get_fields(); + + my @easy_fields = grep {$obj_fields->need($_) && exists($fields->{$_}{'model_accessor'})} keys(%$fields); + + my $pre_process_fields = {}; + foreach my $field (@easy_fields) { + my $model_accessor = $fields->{$field}{'model_accessor'}; + my $fk_fields = join('#', @{$fields->{$field}{'fk_fields'}}); + my $count = 1; + map {$pre_process_fields->{$model_accessor}{$fk_fields}{'fields'}{$_} = TRUE} @{$fields->{$field}{'fields'}}, + grep {!($count++ & 1)} @{$fields->{$field}{'fk_fields'}}; + $pre_process_fields->{$model_accessor}{$fk_fields}{'fk_fields'} = $fields->{$field}{'fk_fields'}; + } + + my $DATA; + + foreach my $model (keys(%$pre_process_fields)) { + foreach my $fk_fields (keys(%{$pre_process_fields->{$model}})) { + next if exists($DATA->{$model}{$fk_fields}); + my $filter = {}; + my $i = 0; + while ($i < @{$pre_process_fields->{$model}{$fk_fields}{'fk_fields'}} - 1) { + my $j = $i + 1; + $filter->{$pre_process_fields->{$model}{$fk_fields}{'fk_fields'}[$j]} = + array_uniq(map {$_->{$pre_process_fields->{$model}{$fk_fields}{'fk_fields'}[$i]}} @$data); + $i += 2; + } + + $DATA->{$model}{$fk_fields} = $self->$model->get_all( + fields => [keys(%{$pre_process_fields->{$model}{$fk_fields}{'fields'}})], + filter => $filter + ); + } + } + + foreach my $field (@easy_fields) { + my $model = $fields->{$field}{'model_accessor'}; + my $fk_fields = join('#', @{$fields->{$field}{'fk_fields'}}); + my $result = $fields->{$field}{'result'} || 'SCALAR'; + if (($result eq 'SCALAR' || $result eq 'HASH') + && !exists($obj_fields->{'__GROUP_DATA__'}{$model}{$fk_fields}{'SCALAR_HASH'})) + { + foreach $data (@{$DATA->{$model}{$fk_fields}}) { + my $count = 1; + my @key_list = map {$data->{$_}} grep {!($count++ & 1)} @{$fields->{$field}{'fk_fields'}}; + $self->_add_key_with_hash(\$obj_fields->{'__GROUP_DATA__'}{$model}{$fk_fields}{'SCALAR_HASH'}, + \@key_list, 1, $data); + } + } elsif ($result eq 'ARRAY' && !exists($obj_fields->{'__GROUP_DATA__'}{$model}{$fk_fields}{'ARRAY'})) { + foreach my $data (@{$DATA->{$model}{$fk_fields}}) { + my $count = 1; + my @key_list = map {$data->{$_}} grep {!($count++ & 1)} @{$fields->{$field}{'fk_fields'}}; + $self->_add_key_with_array(\$obj_fields->{'__GROUP_DATA__'}{$model}{$fk_fields}{'ARRAY'}, + \@key_list, 1, $data); + } + } else { + throw gettext('Unknown type of result "%s"', $result); + } + } +} + +sub _add_key_with_hash { + my ($self, $hash, $key_list, $num, $value) = @_; + + if (@$key_list == $num) { + $$hash->{$key_list->[$num - 1]} = $value; + return TRUE; + } + + $self->_add_key_with_hash(\$$hash->{$key_list->[$num - 1]}, $key_list, ++$num, $value); +} + +sub _add_key_with_array { + my ($self, $hash, $key_list, $num, $value) = @_; + + if (@$key_list == $num) { + push(@{$$hash->{$key_list->[$num - 1]}}, $value); + return TRUE; + } + + $self->_add_key_with_array(\$$hash->{$key_list->[$num - 1]}, $key_list, ++$num, $value); +} + sub _get_fields_obj { my ($self, $fields) = @_; diff --git a/lib/QBit/Application/Model/DBManager/_Utils/Fields.pm b/lib/QBit/Application/Model/DBManager/_Utils/Fields.pm index d254461..8351f01 100644 --- a/lib/QBit/Application/Model/DBManager/_Utils/Fields.pm +++ b/lib/QBit/Application/Model/DBManager/_Utils/Fields.pm @@ -90,6 +90,34 @@ sub process_data { my $val; if (exists($rec->{$field})) { $val = $rec->{$field}; + } elsif (exists($self->{'__FIELDS__'}{$field}{'model_accessor'})) { + my $model = $self->{'__FIELDS__'}{$field}{'model_accessor'}; + my $fk_fields = join('#', @{$self->{'__FIELDS__'}{$field}{'fk_fields'}}); + my $result = $self->{'__FIELDS__'}{$field}{'result'} || 'SCALAR'; + my $count = 1; + my @key_list = map {$rec->{$_}} grep {$count++ & 1} @{$self->{'__FIELDS__'}{$field}{'fk_fields'}}; + if ($result eq 'SCALAR') { + my $value = + $self->_get_value($self->{'__GROUP_DATA__'}{$model}{$fk_fields}{'SCALAR_HASH'}, \@key_list, 1); + $val = $value->{$self->{'__FIELDS__'}{$field}{'fields'}[0]}; + } elsif ($result eq 'HASH') { + my $value = + $self->_get_value($self->{'__GROUP_DATA__'}{$model}{$fk_fields}{'SCALAR_HASH'}, \@key_list, 1); + $val = {map {$_ => $value->{$_}} @{$self->{'__FIELDS__'}{$field}{'fields'}}}; + } elsif ($result eq 'ARRAY') { + my $value = + $self->_get_value($self->{'__GROUP_DATA__'}{$model}{$fk_fields}{'ARRAY'}, \@key_list, 1); + if (@{$self->{'__FIELDS__'}{$field}{'fields'}} == 1) { + $val = [map {$_->{$self->{'__FIELDS__'}{$field}{'fields'}[0]}} @$value]; + } else { + $val = []; + foreach my $row (@$value) { + push(@$val, {map {$_ => $row->{$_}} @{$self->{'__FIELDS__'}{$field}{'fields'}}}); + } + } + } + $val = $self->{'__FIELDS__'}{$field}{'get'}($self, $rec, $val) + if exists($self->{'__FIELDS__'}{$field}{'get'}); } elsif (exists($self->{'__FIELDS__'}{$field}{'get'})) { $val = $rec->{$field} = $self->{'__FIELDS__'}{$field}{'get'}($self, $rec); } elsif ($self->{'__FIELDS__'}{$field}{'i18n'}) { @@ -105,6 +133,16 @@ sub process_data { return \@res; } +sub _get_value { + my ($self, $hash, $key_list, $num) = @_; + + if (@$key_list == $num) { + return $hash->{$key_list->[$num - 1]}; + } else { + return $self->_get_value($hash->{$key_list->[$num - 1]}, $key_list, ++$num); + } +} + sub need { my ($self, $name) = @_; diff --git a/t/lib/Models/Model1.pm b/t/lib/Models/Model1.pm new file mode 100644 index 0000000..0a11574 --- /dev/null +++ b/t/lib/Models/Model1.pm @@ -0,0 +1,60 @@ +package Models::Model1; + +use qbit; +use base qw(QBit::Application::Model::DBManager); #QBit::Application + +__PACKAGE__->model_fields( + id => { + default => TRUE, + db => TRUE, + pk => TRUE, + label => d_gettext('ID'), + }, + domain => { + default => TRUE, + db => TRUE, + label => d_gettext('Domain'), + }, + caption => { + db => TRUE, + label => d_gettext('Caption'), + } +); + +sub get_all { + my ($self, %opts) = @_; + + my %fields = map {$_ => TRUE} @{$opts{'fields'}}; + + foreach (@{$opts{'fields'}}) { + return [ + {creator => "owner 1", id => 1, caption => 'short_caption 1'}, + {creator => "owner 1", id => 2, caption => 'short_caption 1'}, + {creator => "owner 2", id => 3, caption => 'short_caption 2'}, + {creator => "owner 3", id => 4, caption => 'short_caption 3'}, + {creator => "owner 3", id => 5, caption => 'short_caption 3'}, + {creator => "owner 3", id => 6, caption => 'short_caption 3'}, + {creator => "owner 4", id => 7, caption => 'short_caption 4'}, + {creator => "owner 5", id => 8, caption => 'short_caption 5'}, + {creator => "owner 5", id => 9, caption => 'short_caption 5'}, + ] if $_ eq 'creator' && !$fields{'domain'}; + } + + my $result = [ + map { + my $value = $_; + { + id => $value, + map {$_ => "$_ $value"} grep {$_ ne 'id'} @{$opts{'fields'}} + } + } (1 .. 5) + ]; + + foreach (@$result) { + $_->{'creator'} =~ s/creator/owner/ if $_->{'creator'}; + }; + + return $result; +} + +TRUE; diff --git a/t/lib/Models/Model2.pm b/t/lib/Models/Model2.pm new file mode 100644 index 0000000..c8dfcb1 --- /dev/null +++ b/t/lib/Models/Model2.pm @@ -0,0 +1,130 @@ +package Models::Model2; + +use qbit; +use base qw(QBit::Application::Model::DBManager); + +use Test::MockObject::Extends; + +__PACKAGE__->model_accessors(model_1 => 'Models::Model1',); + +__PACKAGE__->model_fields( + f_id => { + default => TRUE, + db => TRUE, + pk => TRUE, + label => d_gettext('ID'), + }, + owner => { + default => TRUE, + db => TRUE, + label => d_gettext('Owner'), + }, + short_caption => { + default => TRUE, + db => TRUE, + label => d_gettext('Short caption'), + }, + name => { + depends_on => ['f_id'], + model_accessor => 'model_1', + fk_fields => ['f_id' => 'id'], + fields => ['caption'], + result => 'SCALAR', + }, + name_array => { + depends_on => ['f_id'], + model_accessor => 'model_1', + fk_fields => ['f_id' => 'id'], + fields => ['caption'], + result => 'ARRAY', + }, + name_hash => { + depends_on => ['f_id'], + model_accessor => 'model_1', + fk_fields => ['f_id' => 'id'], + fields => ['caption'], + result => 'HASH', + }, + info => { + depends_on => ['f_id'], + model_accessor => 'model_1', + fk_fields => ['f_id' => 'id'], + fields => ['caption', 'domain'], + result => 'HASH', + }, + ids_by_owner => { + depends_on => ['owner'], + model_accessor => 'model_1', + fk_fields => ['owner' => 'creator'], + fields => ['id'], + result => 'ARRAY', + }, + ids_with_owner => { + depends_on => ['owner'], + model_accessor => 'model_1', + fk_fields => ['owner' => 'creator'], + fields => ['id', 'creator'], + result => 'ARRAY', + }, + ids_string_by_owner => { + depends_on => ['owner'], + model_accessor => 'model_1', + fk_fields => ['owner' => 'creator'], + fields => ['id'], + result => 'ARRAY', + get => sub { + join(', ', @{$_[2]}); + } + }, + two_fk_fields => { + depends_on => ['f_id', 'owner'], + model_accessor => 'model_1', + fk_fields => ['f_id' => 'id', 'owner' => 'creator'], + fields => ['domain'], + result => 'SCALAR', + }, + two_fk_fields_hash => { + depends_on => ['f_id', 'owner'], + model_accessor => 'model_1', + fk_fields => ['f_id' => 'id', 'owner' => 'creator'], + fields => ['domain', 'creator'], + result => 'HASH', + }, + two_fk_fields_array => { + depends_on => ['short_caption', 'owner'], + model_accessor => 'model_1', + fk_fields => ['short_caption' => 'caption', 'owner' => 'creator'], + fields => ['id'], + result => 'ARRAY', + }, +); + +sub query { + my ($self, %opts) = @_; + + my $fields = $opts{'fields'}->get_db_fields(); + + my $query = Test::MockObject::Extends->new(); + + $query->mock( + 'get_all', + sub { + + my @result = (); + for my $count (1 .. 5) { + my $var->{'f_id'} = $count; + foreach my $field (grep {$_ ne 'f_id'} keys(%$fields)) { + $var->{$field} = "$field $count"; + } + push (@result, $var); + } + return \@result; + } + ); + + $query->mock('all_langs', sub {return $query}); + + return $query; +} + +TRUE; diff --git a/t/lib/TestApp.pm b/t/lib/TestApp.pm new file mode 100644 index 0000000..881e966 --- /dev/null +++ b/t/lib/TestApp.pm @@ -0,0 +1,9 @@ +package TestApp; + +use qbit; +use base qw(QBit::Application); + +use Models::Model1 accessor => 'model_1'; +use Models::Model2 accessor => 'model_2'; + +TRUE; diff --git a/t/qbit-application-model-dbmanager.t b/t/qbit-application-model-dbmanager.t new file mode 100644 index 0000000..c347d76 --- /dev/null +++ b/t/qbit-application-model-dbmanager.t @@ -0,0 +1,292 @@ +#!/usr/bin/perl -w + +use qbit; +use Test::More; +use Test::MockObject::Extends; + +use TestApp; + +my $app = new TestApp(); + +$app->{'timelog'} = Test::MockObject::Extends->new($app->{'timelog'}); + +$app->timelog->mock('start', sub { }); + +$app->timelog->mock('finish', sub { }); + +is_deeply( + $app->model_2->get_all(fields => ['name']), + [ + {'name' => 'caption 1'}, + {'name' => 'caption 2'}, + {'name' => 'caption 3'}, + {'name' => 'caption 4'}, + {'name' => 'caption 5'} + ], + 'type: SCALAR' +); + +is_deeply( + $app->model_2->get_all(fields => ['name_array']), + [ + {'name_array' => ['caption 1']}, + {'name_array' => ['caption 2']}, + {'name_array' => ['caption 3']}, + {'name_array' => ['caption 4']}, + {'name_array' => ['caption 5']} + ], + 'type: ARRAY' +); + +is_deeply( + $app->model_2->get_all(fields => ['name_hash']), + [ + {'name_hash' => {caption => 'caption 1'}}, + {'name_hash' => {caption => 'caption 2'}}, + {'name_hash' => {caption => 'caption 3'}}, + {'name_hash' => {caption => 'caption 4'}}, + {'name_hash' => {caption => 'caption 5'}} + ], + 'type: HASH' +); + +is_deeply( + $app->model_2->get_all(fields => ['info']), + [ + { + 'info' => { + caption => 'caption 1', + domain => 'domain 1' + } + }, + { + 'info' => { + caption => 'caption 2', + domain => 'domain 2' + } + }, + { + 'info' => { + caption => 'caption 3', + domain => 'domain 3' + } + }, + { + 'info' => { + caption => 'caption 4', + domain => 'domain 4' + } + }, + { + 'info' => { + caption => 'caption 5', + domain => 'domain 5' + } + } + ], + 'type: HASH with multiple keys' +); + +is_deeply( + $app->model_2->get_all(fields => ['f_id', 'ids_by_owner']), + [ + { + 'f_id' => 1, + 'ids_by_owner' => [1, 2] + }, + { + 'f_id' => 2, + 'ids_by_owner' => [3] + }, + { + 'f_id' => 3, + 'ids_by_owner' => [4, 5, 6] + }, + { + 'f_id' => 4, + 'ids_by_owner' => [7] + }, + { + 'f_id' => 5, + 'ids_by_owner' => [8, 9] + }, + ], + 'type: ARRAY with multiple result' +); + +is_deeply( + $app->model_2->get_all(fields => ['f_id', 'ids_with_owner']), + [ + { + 'f_id' => 1, + 'ids_with_owner' => [ + { + id => 1, + creator => 'owner 1' + }, + { + id => 2, + creator => 'owner 1' + } + ] + }, + { + 'f_id' => 2, + 'ids_with_owner' => [ + { + id => 3, + creator => 'owner 2' + } + ] + }, + { + 'f_id' => 3, + 'ids_with_owner' => [ + { + id => 4, + creator => 'owner 3' + }, + { + id => 5, + creator => 'owner 3' + }, + { + id => 6, + creator => 'owner 3' + } + ] + }, + { + 'f_id' => 4, + 'ids_with_owner' => [ + { + id => 7, + creator => 'owner 4' + } + ] + }, + { + 'f_id' => 5, + 'ids_with_owner' => [ + { + id => 8, + creator => 'owner 5' + }, + { + id => 9, + creator => 'owner 5' + } + ] + }, + ], + 'type: ARRAY with hash items' +); + +is_deeply( + $app->model_2->get_all(fields => ['ids_string_by_owner']), + [ + {'ids_string_by_owner' => '1, 2'}, + {'ids_string_by_owner' => '3'}, + {'ids_string_by_owner' => '4, 5, 6'}, + {'ids_string_by_owner' => '7'}, + {'ids_string_by_owner' => '8, 9'} + ], + 'method get after getting data' +); + +is_deeply( + $app->model_2->get_all(fields => ['two_fk_fields']), + [ + { + 'two_fk_fields' => 'domain 1' + }, + { + 'two_fk_fields' => 'domain 2' + }, + { + 'two_fk_fields' => 'domain 3' + }, + { + 'two_fk_fields' => 'domain 4' + }, + { + 'two_fk_fields' => 'domain 5' + } + ], + 'two fk_fields, type: SCALAR' +); + +is_deeply( + $app->model_2->get_all(fields => ['two_fk_fields_hash']), + [ + { + 'two_fk_fields_hash' => { + 'domain' => 'domain 1', + 'creator' => 'owner 1' + } + }, + { + 'two_fk_fields_hash' => { + 'domain' => 'domain 2', + 'creator' => 'owner 2' + } + }, + { + 'two_fk_fields_hash' => { + 'domain' => 'domain 3', + 'creator' => 'owner 3' + } + }, + { + 'two_fk_fields_hash' => { + 'domain' => 'domain 4', + 'creator' => 'owner 4' + } + }, + { + 'two_fk_fields_hash' => { + 'domain' => 'domain 5', + 'creator' => 'owner 5' + } + } + ], + 'two fk_fields, type: HASH' +); + +is_deeply( + $app->model_2->get_all(fields => ['two_fk_fields_array']), + [ + { + 'two_fk_fields_array' => [ + 1, + 2 + ] + }, + { + 'two_fk_fields_array' => [ + 3 + ] + }, + { + 'two_fk_fields_array' => [ + 4, + 5, + 6 + ] + }, + { + 'two_fk_fields_array' => [ + 7 + ] + }, + { + 'two_fk_fields_array' => [ + 8, + 9 + ] + } + ], + 'two fk_fields, type: ARRAY' +); + +done_testing();