File Coverage

lib/DBIx/DR/Iterator.pm
Criterion Covered Total %
statement 140 169 82.8
branch 69 116 59.4
condition 19 47 40.4
subroutine 23 27 85.1
pod 10 13 76.9
total 261 372 70.1


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