File Coverage

lib/DBIx/DR/Iterator.pm
Criterion Covered Total %
statement 163 184 88.5
branch 80 124 64.5
condition 24 52 46.1
subroutine 25 28 89.2
pod 10 14 71.4
total 302 402 75.1


line stmt bran cond sub pod time code
1 3     3   47498 use utf8;
  3         4  
  3         12  
2 3     3   67 use strict;
  3         2  
  3         41  
3 3     3   8 use warnings;
  3         9  
  3         685  
4              
5             package DBIx::DR::Iterator;
6 3     3   10 use Scalar::Util qw(blessed weaken);
  3         3  
  3         196  
7 3     3   634 use DBIx::DR::Util;
  3         9  
  3         121  
8 3     3   12 use Carp;
  3         4  
  3         3838  
9              
10              
11             # Perl 5.18 refuses smartmatch
12             my $is = sub($$) {
13             my ($v1, $v2) = @_;
14             return 0 if defined($v1) xor defined($v2);
15             return 1 unless defined $v1;
16             return $v1 eq $v2;
17             };
18              
19              
20             sub new {
21 22     22 1 11055 my ($class, $fetch, %opts) = @_;
22              
23 22         28 my ($is_hash, $is_array) = (0, 0);
24              
25 22         17 my $count;
26              
27 22 100       60 if ('ARRAY' eq ref $fetch) {
    50          
28 12         9 $is_array = 1;
29 12 50       26 if ($count = @$fetch) {
30 12 50 66     46 croak 'You must use array of hashrefs'
31             unless 'HASH' eq ref $fetch->[0] or blessed $fetch->[0];
32             }
33             } elsif ('HASH' eq ref $fetch) {
34 10         8 $is_hash = 1;
35 10         22 my ($k) = each %$fetch;
36 10 50       24 if ($count = keys %$fetch) {
37             croak 'You must use hash of hashrefs'
38 10 50 66     41 unless 'HASH' eq ref $fetch->{$k} or blessed $fetch->{$k};
39             }
40             } else {
41 0         0 croak "You should bless 'HASHREF' or 'ARRAYREF' value";
42             }
43              
44              
45             my ($item_class, $item_constructor) =
46 22   100     65 camelize($opts{'-item'} || 'dbix-dr-iterator-item#new');
47              
48 22         31 my $keep_blessed = $opts{-keep_blessed};
49 22   100     63 $keep_blessed //= 1;
50              
51              
52             return bless {
53             fetch => $fetch,
54             is_hash => $is_hash,
55             is_array => $is_array,
56             count => $count,
57             iterator => 0,
58             item_class => $item_class,
59             item_constructor => $item_constructor,
60             is_changed => 0,
61             keep_blessed => $keep_blessed,
62 22 100 66     223 noitem_iter => $opts{-noitem_iter} ? 1 : 0,
63             } => ref($class) || $class;
64             }
65              
66              
67             sub is_changed {
68 8     8 1 8 my ($self, $value) = @_;
69 8 100       17 $self->{is_changed} = $value ? 1 : 0 if @_ > 1;
    100          
70 8         14 return $self->{is_changed};
71             }
72              
73              
74             sub count {
75 24     24 1 4011 my ($self) = @_;
76 24         69 return $self->{count};
77             }
78              
79              
80             sub reset {
81 3     3 1 4 my ($self) = @_;
82 3         3 $self->{iterator} = 0;
83 3 100       6 keys %{ $self->{fetch} } if $self->{is_hash};
  2         3  
84 3         3 return;
85             }
86              
87              
88             sub next : method {
89 77     77 1 12099 my ($self) = @_;
90              
91 77 100       166 if ($self->{is_array}) {
92             return $self->get($self->{iterator}++)
93 38 100       101 if $self->{iterator} < $self->{count};
94 8         9 $self->{iterator} = 0;
95 8         13 return;
96             }
97              
98 39         26 my ($k) = each %{ $self->{fetch} };
  39         74  
99 39 100       63 return unless defined $k;
100 32         47 return $self->get($k);
101             }
102              
103              
104             sub get {
105 118     118 1 1123 my ($self, $name) = @_;
106 118 50 33     374 croak "Usage \$collection->get('name|number')"
107             if @_ <= 1 or !defined($name);
108 118         80 my $item;
109 118 100       138 if ($self->{is_array}) {
110 51 50       171 croak "Element number must be digit value" unless $name =~ /^-?\d+$/;
111             croak "Element number is out of arraybound"
112 51 50 33     154 if $name >= $self->{count} || $name < -$self->{count};
113 51         49 $item = $self->{fetch}[ $name ];
114             } else {
115 67 50       93 croak "Key '$name' is not exists" unless exists $self->{fetch}{$name};
116 67         58 $item = $self->{fetch}{ $name };
117             }
118              
119 118 100       251 unless(blessed $item) {
120 51 100       81 if (my $method = $self->{item_constructor}) {
121             $item = $self->{item_class}->$method(
122             $item,
123 39 100       137 ( $self->{noitem_iter} ? () : $self )
124             );
125            
126 39 100 66     216 if (blessed($item) and $self->{keep_blessed}) {
127 30 100       41 if ($self->{is_array}) {
128 18         22 $self->{fetch}[ $name ] = $item;
129             } else {
130 12         12 $self->{fetch}{ $name } = $item;
131             }
132             }
133             } else {
134 12         15 bless $item => $self->{item_class};
135             }
136              
137             }
138 118         229 return $item;
139             }
140              
141              
142             sub exists {
143 6     6 1 10 my ($self, $name) = @_;
144 6 50 33     27 croak "Usage \$collection->exists('name|number')"
145             if @_ <= 1 or !defined($name);
146              
147 6 100       10 if ($self->{is_array}) {
148 2 50       9 croak "Element number must be digit value" unless $name =~ /^\d+$/;
149 2 50 33     8 return 0 if $name >= $self->{count} || $name < -$self->{count};
150 2         6 return 1;
151             }
152              
153 4   50     15 return exists($self->{fetch}{$name}) || 0;
154             }
155              
156              
157             sub all {
158 7     7 1 12 my ($self, $field) = @_;
159 7 50       13 return unless defined wantarray;
160 7         8 my @res;
161 7 100       11 if ($self->{is_array}) {
162 5         5 for (my $i = 0; $i < @{ $self->{fetch} }; $i++) {
  20         30  
163 15         19 push @res => $self->get($i);
164             }
165             } else {
166 2         3 push @res => $self->get($_) for keys %{ $self->{fetch} };
  2         9  
167             }
168              
169 7 50       11 @res = map { $_->$field } @res if $field;
  0         0  
170              
171 7         33 return @res;
172             }
173              
174             sub grep : method {
175 5     5 1 539 my ($self, $key, $value) = @_;
176              
177 5         5 my $cb;
178 5 100       8 if ('CODE' eq ref $key) {
179 2         2 $cb = $key;
180             } else {
181 3     13   10 $cb = sub { $is->($_[0]->$key, $value) };
  13         32  
182             }
183              
184 5         4 my $obj;
185 5 100       7 if ($self->{is_array}) {
186 2         4 $obj = [ grep { $cb->($_) } $self->all ];
  6         6  
187             } else {
188             $obj = {
189 6         12 map {( $_ => $self->get($_) )}
190 15         17 grep { $cb->( $self->get($_) ) }
191 3         4 keys %{ $self->{fetch} }
  3         6  
192             };
193             }
194              
195             return $self->new(
196             $obj,
197             -item => decamelize($self->{item_class}, $self->{item_constructor})
198 5         16 );
199             }
200              
201             sub first {
202 0     0 0 0 my ($self) = @_;
203              
204 0 0       0 if ($self->{is_array}) {
205 0 0       0 return ($self->{iterator} == 1) ? 1 : 0;
206             }
207              
208 0         0 croak "'first' and 'last' methods aren't provided for hashiterators";
209 0         0 return;
210             }
211              
212             sub last : method {
213 0     0 0 0 my ($self) = @_;
214              
215 0 0       0 if ($self->{is_array}) {
216 0 0       0 return ($self->{iterator} == $self->{count}) ? 1 : 0;
217             }
218              
219 0         0 croak "'first' and 'last' methods aren't provided for hashiterators";
220 0         0 return;
221             }
222              
223              
224             sub push : method {
225 1     1 1 7 my ($self, $k, $v) = @_;
226              
227 1 50       4 if ($self->{is_hash}) {
228 0 0       0 croak 'Usage $it->push(key => $value)' unless @_ >= 3;
229 0 0       0 croak 'Value is undefined' unless defined $v;
230 0 0 0     0 croak "Value isn't HASHREF or object"
231             unless 'HASH' eq ref $v or blessed $v;
232 0 0       0 $self->{count}++ unless exists $self->{fetch}{$k};
233 0         0 $self->{fetch}{$k} = $v;
234 0         0 $self->is_changed(1);
235 0         0 return $self->get($k);
236             }
237              
238 1 50       2 croak "Value isn't defined" unless defined $k;
239 1 50 33     4 croak "Value isn't HASHREF or object"
240             unless 'HASH' eq ref $k or blessed $k;
241 1         1 push @{ $self->{fetch} }, $k;
  1         2  
242 1         2 $self->{count}++;
243 1         3 return $self->get(-1);
244             }
245              
246             sub pop : method {
247 1     1 0 2 my ($self) = @_;
248 1 50       4 croak "Can't use pop method with HASHREF iterator" if $self->{is_hash};
249 1 50       4 return undef unless $self->{count};
250              
251 1         2 my $item = $self->get(-1);
252 1         3 pop @{ $self->{fetch} };
  1         3  
253 1         3 $self->{count}--;
254 1         7 $item;
255             }
256              
257              
258             sub find : method {
259 1     1 0 6 my ($self, $field, $value) = @_;
260              
261 1         2 $self->reset;
262 1         2 while(my $item = $self->next) {
263 3 100       9 return $item if $is->($item->$field, $value);
264             }
265 0         0 return;
266             }
267              
268              
269              
270             package DBIx::DR::Iterator::Item;
271 3     3   13 use Scalar::Util ();
  3         3  
  3         36  
272 3     3   8 use Carp ();
  3         3  
  3         1164  
273              
274             # to exclude this method from AUTOLOAD
275       0     sub DESTROY {}
276              
277             sub AUTOLOAD {
278 113     113   1435 our $AUTOLOAD;
279 113         343 my ($method) = $AUTOLOAD =~ /.*::(.*)/;
280 113         104 my ($self, $value) = @_;
281              
282 113 50       168 Carp::croak "Can't find method '$self->$method'" unless ref $self;
283             Carp::croak "Can't find method '$method' in this item"
284 113 100       267 unless exists $self->{$method};
285              
286 112 100       147 if (@_ > 1) {
287 4         3 my $is_changed;
288              
289 4 100 100     24 if (ref $value and ref $self->{$method}) {
    100 33        
    50 0        
    0          
290             $is_changed = Scalar::Util::refaddr($value)
291 2         7 != Scalar::Util::refaddr($self->{$method});
292             } elsif(ref($value) ne ref($self->{$method})) {
293 1         2 $is_changed = 1;
294             } elsif(defined $value and defined $self->{$method}) {
295 1         2 $is_changed = $value ne $self->{$method};
296             } elsif(defined $value xor defined $self->{$method}) {
297 0         0 $is_changed = 1;
298             }
299              
300 4 100       14 $self->is_changed(1) if $is_changed;
301 4         5 $self->{$method} = $value;
302             }
303              
304 112         315 return $self->{$method};
305             }
306              
307             sub new {
308 22     22   25 my ($class, $object, $iterator) = @_;
309 22 50       40 return unless defined $object;
310 22 50       46 Carp::croak "Usage: DBIx::DR::Iterator::Item->new(HASHREF [, iterator ])"
311             unless 'HASH' eq ref $object;
312 22   33     64 my $self = bless $object => ref($class) || $class;
313 22         38 $self->{iterator} = $iterator;
314 22         48 Scalar::Util::weaken($self->{iterator});
315 22         24 $self->{is_changed} = 0;
316 22         50 return $self;
317             }
318              
319             sub is_changed {
320 8     8   10 my ($self, $value) = @_;
321 8 100       15 if (@_ > 1) {{
322 4 100       3 $self->{is_changed} = $value ? 1 : 0;
  4         7  
323              
324 4 100       12 last unless $self->{is_changed};
325 3 50       12 last unless Scalar::Util::blessed $self->{iterator};
326 3 50       14 last unless $self->{iterator}->can('is_changed');
327 3         5 $self->{iterator}->is_changed( 1 );
328             }}
329 8         35 return $self->{is_changed};
330             }
331              
332             sub can {
333 1     1   1 my ($self, $method) = @_;
334 1 50 33     6 return 1 if ref $self and exists $self->{$method};
335 1         22 return $self->SUPER::can($method);
336             }
337              
338              
339             1;
340              
341             =head1 NAME
342              
343             DBIx::DR::Iterator - iterator for L.
344              
345             =head1 SYNOPSIS
346              
347             my $it = DBIx::DR::Iterator->new($arrayref);
348              
349             printf "Rows count: %d\n", $it->count;
350              
351             while(my $row == $it->next) {
352             print "Row: %s\n", $row->field;
353             }
354              
355             my $row = $it->get(15); # element 15
356              
357              
358              
359             my $it = DBIx::DR::Iterator->new($hashref);
360              
361             printf "Rows count: %d\n", $it->count;
362              
363             while(my $row == $it->next) {
364             print "Row: %s\n", $row->field;
365             }
366              
367             my $row = $it->get('abc'); # element with key name eq 'abc'
368              
369              
370             =head1 DESCRIPTION
371              
372             The package constructs iterator from HASHREF or ARRAYREF value.
373              
374             =head1 Methods
375              
376             =head2 new
377              
378             Constructor.
379              
380             my $i = DBIx::DR::Iterator->new($arrayset [, OPTIONS ]);
381              
382             Where B are:
383              
384             =over
385              
386             =item -item => 'decamelized_obj_define';
387              
388             It will bless (or construct) row into specified class. See below.
389              
390             By default it constructs L objects.
391              
392             =item -noitem_iter => TRUE|FALSE
393              
394             Don't pass iterator to item constructor.
395              
396             =item -keep_blessed => TRUE|FALSE
397              
398             Force store blessed item into iterator. Default value is C.
399              
400             =back
401              
402             =head2 count
403              
404             Returns count of elements.
405              
406             =head2 is_changed
407              
408             Returns (or set) flag that one of contained elements was changed.
409              
410             =head2 exists(name|number)
411              
412             Returns B if element 'B' is exists.
413              
414             =head2 get(name|number)
415              
416             Returns element by 'B'. It will throw exception if element
417             isn't L.
418              
419             =head2 next
420              
421             Returns next element or B.
422              
423             =head2 reset
424              
425             Resets internal iterator (that is used by L).
426              
427             =head2 all
428              
429             Returns all elements (as an array).
430              
431             If You notice an argument it will extract specified fields:
432              
433             my @ids = $it->all('id');
434              
435             The same as:
436              
437             my @ids = map { $_->id } $it->all;
438              
439             =head2 grep
440              
441             Constructs new iterator that is subset of parent iterator.
442              
443             my $busy = $list->grep(sub { $_[0]->busy ? 1 : 0 });
444              
445             =head2 push
446              
447             Pushes one element into iterator.
448              
449             If You use HASH-iterator You have to note key name.
450              
451             =head3 Example
452              
453             $hiter->push(abc => { id => 1 });
454             $hiter->push(abc => $oiter->get('abc'));
455              
456             $aiter->push({ id => 1 });
457              
458             =head1 DBIx::DR::Iterator::Item
459              
460             One row. It has methods names coincident with field names. Also it has a few
461             additional methods:
462              
463             =head2 new
464              
465             Constructor. Receives two arguments: B and link to
466             L.
467              
468             my $row = DBIx::DR::Iterator::Item->new({ id => 1 });
469             $row = DBIx::DR::Iterator::Item->new({ id => 1 }, $iterator); }
470              
471             =head2 iterator
472              
473             Returns (or set) iterator object. The link is created by constructor.
474             This is a L link.
475              
476             =head2 is_changed
477              
478             Returns (or set) flag if the row has been changed. If You change any of
479             row's fields the flag will be set. Also iterator's flag will be set.
480              
481              
482             =head1 COPYRIGHT
483              
484             Copyright (C) 2011 Dmitry E. Oboukhov
485             Copyright (C) 2011 Roman V. Nikolaev
486              
487             This program is free software, you can redistribute it and/or
488             modify it under the terms of the Artistic License.
489              
490             =cut
491