File Coverage

lib/DBIx/DR/Iterator.pm
Criterion Covered Total %
statement 147 175 84.0
branch 74 120 61.6
condition 23 52 44.2
subroutine 23 27 85.1
pod 10 13 76.9
total 277 387 71.5


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