File Coverage

lib/DBIx/EAV/ResultSet.pm
Criterion Covered Total %
statement 116 134 86.5
branch 41 50 82.0
condition 12 14 85.7
subroutine 26 31 83.8
pod 7 19 36.8
total 202 248 81.4


line stmt bran cond sub pod time code
1             package DBIx::EAV::ResultSet;
2              
3 10     10   61 use Moo;
  10         16  
  10         52  
4 10     10   2753 use DBIx::EAV::Entity;
  10         18  
  10         232  
5 10     10   3613 use DBIx::EAV::Cursor;
  10         30  
  10         352  
6 10     10   66 use Data::Dumper;
  10         20  
  10         537  
7 10     10   78 use Carp qw/ croak confess /;
  10         17  
  10         512  
8             use overload
9 10         74 '0+' => "_to_num",
10             'bool' => "_to_bool",
11 10     10   58 fallback => 1;
  10         17  
12              
13             my $sql = SQL::Abstract->new;
14              
15             has 'eav', is => 'ro', required => 1;
16             has 'type', is => 'ro', required => 1;
17             has '_query', is => 'rw', default => sub { [] }, init_arg => 'query';
18             has '_options', is => 'rw', default => sub { {} }, init_arg => 'options';
19             has 'cursor', is => 'rw',
20             lazy => 1,
21             init_arg => undef,
22             predicate => '_has_cursor',
23             clearer => '_clear_cursor',
24             builder => '_build_cursor';
25              
26             has 'entity_class', is => 'ro', init_arg => undef, lazy => 1, default => sub {
27             my $self = shift;
28             $self->eav->_resolve_entity_class($self->type->name) || 'DBIx::EAV::Entity';
29             };
30              
31 3     3   279 sub _to_num { $_[0]->count }
32              
33 0     0   0 sub _to_bool { 1 }
34              
35              
36             sub _build_cursor {
37 78     78   1272 my $self = shift;
38              
39 78         1670 DBIx::EAV::Cursor->new(
40             eav => $self->eav,
41             type => $self->type,
42             query => $self->_query,
43             options => $self->_options,
44             );
45             }
46              
47              
48             sub new_entity {
49 82     82 1 196 my ($self, $data) = @_;
50 82         1799 my $entity = $self->entity_class->new( eav => $self->eav, type => $self->type );
51 82 50       762 $entity->set($data) if ref $data eq 'HASH';
52 82         274 $entity;
53             }
54              
55              
56             sub inflate_entity {
57 75     75 0 151 my ($self, $data) = @_;
58 75         190 my $type = $self->type;
59             $type = $self->eav->type_by_id($data->{entity_type_id})
60 75 100 100     419 if $data->{entity_type_id} && $data->{entity_type_id} != $type->id;
61              
62 75         1785 my $entity = $self->entity_class->new( eav => $self->eav, type => $type, raw => $data );
63 75         641 $entity->load_attributes;
64 75         389 $entity;
65             }
66              
67              
68             {
69 10     10   5119 no warnings;
  10         21  
  10         13155  
70             *create = \&insert;
71             }
72              
73             sub insert {
74 77     77 1 419 my ($self, $data) = @_;
75 77         208 $self->new_entity($data)->save;
76             }
77              
78              
79             sub populate {
80 16     16 1 295 my ($self, $data) = @_;
81 16 50       62 die 'Call populate(\@items)' unless ref $data eq 'ARRAY';
82              
83 16         28 my @result;
84 16         35 foreach my $item (@$data) {
85 46         118 push @result, $self->insert($item);
86             }
87              
88 16 100       83 return wantarray ? @result : \@result;
89             }
90              
91              
92             sub update {
93 0     0 0 0 my ($self, $data, $where) = @_;
94              
95 0   0     0 $where //= {};
96 0         0 $where->{entity_type_id} = $self->type->id;
97              
98             # do a direct update for static attributes
99              
100             }
101              
102              
103             sub delete {
104 3     3 1 21 my $self = shift;
105 3         9 my $eav = $self->eav;
106 3         8 my $type = $self->type;
107 3         41 my $entities_table = $eav->table('entities');
108              
109             # Call delete_all for SQLite since it doesn't
110             # support delete with joins.
111             # Better solution welcome.
112 3 50       43 return $self->delete_all if
113             $self->eav->schema->db_driver_name eq 'SQLite';
114              
115 0 0       0 unless ($eav->schema->database_cascade_delete) {
116              
117             # delete links by relationship id
118 0         0 my @ids = map { $_->{id} } $type->relationships;
  0         0  
119              
120 0         0 $eav->table('entity_relationships')->delete(
121             {
122             relationship_id => \@ids,
123             $entities_table->name.'.entity_type_id' => $type->id
124             },
125             { join => { $entities_table->name => [{ 'me.left_entity_id' => 'their.id' }, { 'me.right_entity_id' => 'their.id' }] } }
126             );
127              
128             # delete attributes:
129             # - group attrs by data type so only one DELETE command is sent per data type
130             # - restrict by entity_type_id so we dont delete parent/sibiling/child data
131 0         0 my %types;
132 0         0 push @{ $types{$_->{data_type}} }, $_->{id}
133 0         0 for $type->attributes(no_static => 1);
134              
135 0         0 while (my ($data_type, $ids) = each %types) {
136              
137 0         0 my $value_table = $eav->table('value_'.$data_type);
138 0         0 $value_table->delete(
139             {
140             attribute_id => $ids,
141             $entities_table->name.'.entity_type_id' => $type->id
142             },
143             { join => { $entities_table->name => { 'me.entity_id' => 'their.id' } } }
144             );
145             }
146             }
147              
148 0         0 $entities_table->delete({ entity_type_id => $type->id });
149             }
150              
151              
152             sub delete_all {
153 4     4 1 32 my $self = shift;
154              
155 4 50       19 my $rs = scalar @_ > 0 ? $self->search_rs(@_) : $self;
156 4         8 my $i = 0;
157              
158 4         14 while (my $entity = $rs->next) {
159 7         33 $entity->delete;
160 7         43 $i++;
161             }
162              
163 4         22 $i;
164             }
165              
166              
167             sub find {
168 10     10 0 3274 my ($self, $criteria, $options) = @_;
169              
170 10 50       32 croak "Missing find() criteria."
171             unless defined $criteria;
172              
173             # simple id search
174 10 100       38 return $self->search_rs({ id => $criteria }, $options)->next
175             unless ref $criteria;
176              
177 9         32 my $rs = $self->search_rs($criteria, $options);
178 9         265 my $result = $rs->next;
179              
180             # criteria is a search query, die if this query returns multiple items
181 9 100 100     249 croak "find() returned more than one entity. If this is what you want, use search or search_rs."
182             if defined $result && defined $rs->cursor->next;
183              
184 8         324 $result;
185             }
186              
187              
188             sub search {
189 48     48 0 1417 my ($self, $query, $options) = @_;
190              
191 48         138 my $rs = $self->search_rs($query, $options);
192              
193 48 100       1477 return wantarray ? $rs->all : $rs;
194             }
195              
196             sub search_rs {
197 59     59 0 142 my ($self, $query, $options) = @_;
198              
199             # simple combine queries using AND
200 59         115 my @new_query = @{ $self->_query };
  59         192  
201 59 100       182 push @new_query, $query if $query;
202              
203             # merge options
204 59         167 my $merged_options = $self->_merge_options($options);
205              
206 59         1494 (ref $self)->new(
207             eav => $self->eav,
208             type => $self->type,
209             query => \@new_query,
210             options => $merged_options
211             );
212             }
213              
214              
215             sub _merge_options {
216 59     59   111 my ($self, $options) = @_;
217              
218 59         81 my %merged = %{ $self->_options };
  59         181  
219              
220 59 100       201 return \%merged
221             unless defined $options;
222              
223 19 50       65 confess "WTF" if $options eq '';
224              
225 19         72 foreach my $opt (keys %$options) {
226              
227             # doesnt even exist, just copy
228 39 100       94 if (not exists $merged{$opt}) {
    100          
    100          
229 36         79 $merged{$opt} = $options->{$opt};
230             }
231              
232             # having: combine queries using AND
233             elsif ($opt eq 'having') {
234              
235 1         2 $merged{$opt} = [$merged{$opt}, $options->{$opt}];
236             }
237              
238             # merge array
239             elsif (ref $merged{$opt} eq 'ARRAY') {
240              
241             $merged{$opt} = [
242 1         4 @{$merged{$opt}},
243 1 50       1 ref $options->{$opt} eq 'ARRAY' ? @{$options->{$opt}} : $options->{$opt}
  1         4  
244             ];
245             }
246              
247             else {
248 1         2 $merged{$opt} = $options->{$opt};
249             }
250             }
251              
252 19         51 \%merged;
253             }
254              
255              
256             sub count {
257 17     17 1 58 my $self = shift;
258 17 100       51 return $self->search(@_)->count if @_;
259              
260             # from DBIx::Class::ResultSet::count()
261             # this is a little optimization - it is faster to do the limit
262             # adjustments in software, instead of a subquery
263 14         34 my $options = $self->_options;
264 14         44 my ($limit, $offset) = @$options{qw/ limit offset /};
265              
266 14         35 my $count = $self->_count_rs($options)->cursor->next->{count};
267              
268 14 100       258 $count -= $offset if $offset;
269 14 100       52 $count = 0 if $count < 0;
270 14 100 100     44 $count = $limit if $limit && $count > $limit;
271              
272 14         106 $count;
273             }
274              
275              
276             sub _count_rs {
277 14     14   28 my ($self, $options) = @_;
278 14         70 my %tmp_options = ( %$options, select => [\'COUNT(*) AS count'] );
279              
280             # count using subselect if needed
281             $tmp_options{from} = $self->as_query
282 14 100 100     90 if $options->{group_by} || $options->{distinct};
283              
284 14         42 delete @tmp_options{qw/ limit offset order_by group_by distinct /};
285              
286             (ref $self)->new(
287             eav => $self->eav,
288             type => $self->type,
289 14         56 query => [@{ $self->_query }],
  14         349  
290             options => \%tmp_options
291             );
292             }
293              
294              
295             sub as_query {
296 4     4 0 9 my $self = shift;
297 4         77 $self->cursor->as_query;
298             }
299              
300              
301             sub reset {
302 40     40 0 64 my $self = shift;
303 40         870 $self->_clear_cursor;
304 40         549 $self;
305             }
306              
307             sub first {
308 3     3 0 8 $_[0]->reset->next;
309             }
310              
311             sub next {
312 112     112 0 226 my $self = shift;
313              
314             # fetch next
315 112         2568 my $entity_row = $self->cursor->next;
316 112 100       2050 return unless defined $entity_row;
317              
318             # instantiate entity
319 75         232 $self->inflate_entity($entity_row);
320             }
321              
322             sub all {
323 18     18 0 54 my $self = shift;
324 18         31 my @entities;
325              
326 18         67 $self->reset;
327              
328 18         55 while (my $entity = $self->next) {
329 44         145 push @entities, $entity;
330             }
331              
332 18         62 $self->reset;
333              
334 18 100       105 return wantarray ? @entities : \@entities;
335             }
336              
337             sub pager {
338 0     0 0   die "pager() not implemented";
339             }
340              
341             sub distinct {
342 0     0 1   die "distinct() not implemented";
343             }
344              
345             sub storage_size {
346 0     0 0   die "storage_size() not implemented";
347             }
348              
349              
350             1;
351              
352             __END__