File Coverage

blib/lib/Elastic/Model/Role/Iterator.pm
Criterion Covered Total %
statement 15 116 12.9
branch 0 44 0.0
condition 0 7 0.0
subroutine 5 41 12.2
pod 21 31 67.7
total 41 239 17.1


line stmt bran cond sub pod time code
1             package Elastic::Model::Role::Iterator;
2             $Elastic::Model::Role::Iterator::VERSION = '0.52';
3 1     1   934 use Carp;
  1         3  
  1         73  
4 1     1   5 use Moose::Role;
  1         2  
  1         8  
5 1     1   5216 use MooseX::Types::Moose qw(ArrayRef Int CodeRef);
  1         2  
  1         11  
6 1     1   5622 use namespace::autoclean;
  1         3  
  1         9  
7              
8             #===================================
9             has 'elements' => (
10             #===================================
11                 isa => ArrayRef,
12                 traits => ['Array'],
13                 is => 'ro',
14                 writer => '_set_elements',
15                 handles => { 'get_element' => 'get', },
16                 default => sub { [] },
17             );
18              
19             #has 'page_size' => (
20             # isa => Int,
21             # is => 'rw',
22             # default => 10
23             #);
24              
25             #===================================
26             has '_i' => (
27             #===================================
28                 isa => Int,
29                 is => 'rw',
30                 default => -1,
31             );
32              
33             #===================================
34             has 'wrapper' => (
35             #===================================
36                 isa => CodeRef,
37                 is => 'rw',
38                 lazy => 1,
39                 default => sub {
40                     sub { shift() }
41                 },
42             );
43              
44             #===================================
45             has 'multi_wrapper' => (
46             #===================================
47                 isa => CodeRef,
48                 is => 'rw',
49                 lazy => 1,
50                 default => sub {
51                     sub {@_}
52                 },
53             );
54              
55             #===================================
56             after 'all_elements'
57             #===================================
58                 => sub { shift->reset };
59              
60             #===================================
61             around [ 'wrapper', 'multi_wrapper' ]
62             #===================================
63                 => sub {
64                 my $orig = shift;
65                 my $self = shift;
66                 if (@_) { $self->$orig(@_); return $self }
67                 $self->$orig(@_);
68                 };
69              
70 1     1   320 no Moose;
  1         2  
  1         7  
71              
72             #===================================
73             sub _incr_i {
74             #===================================
75 0     0         my $self = shift;
76 0               my $i = $self->_i + 1;
77 0 0             $self->_i( $i >= $self->size ? -1 : $i );
78             }
79              
80             #===================================
81             sub _decr_i {
82             #===================================
83 0     0         my $self = shift;
84 0               my $i = $self->_i - 1;
85 0 0             $self->_i( $i < -1 ? $self->size - 1 : $i );
86             }
87              
88             #===================================
89 0     0 1   sub size { 0 + @{ shift->elements } }
  0            
90             #===================================
91              
92             #===================================
93             sub index {
94             #===================================
95 0     0 1       my $self = shift;
96 0 0             if (@_) {
97 0                   my $index = my $original = shift;
98 0 0                 if ( defined $index ) {
99 0 0                     my $size = $self->size
100                             or croak("Index ($original) out of bounds. No values.");
101 0 0                     $index += $size
102                             if $index < 0;
103 0 0 0                   croak( "Index ($original) out of bounds. "
104                                 . "Values can be 0.."
105                                 . ( $size - 1 ) )
106                             if $index >= $size || $index < 0;
107                     }
108 0                   else { $index = -1 }
109 0                   $self->_i($index);
110                 }
111 0 0             return $self->_i < 0 ? undef : $self->_i;
112             }
113              
114             #===================================
115 0     0 1   sub reset { shift->_i(-1) }
116             #===================================
117              
118             #===================================
119 0     0 1   sub first { $_[0]->wrapper->( $_[0]->first_element ) }
120 0     0 1   sub last { $_[0]->wrapper->( $_[0]->last_element ) }
121 0     0 1   sub next { $_[0]->wrapper->( $_[0]->next_element ) }
122 0     0 1   sub prev { $_[0]->wrapper->( $_[0]->prev_element ) }
123 0     0 1   sub current { $_[0]->wrapper->( $_[0]->current_element ) }
124 0     0 1   sub peek_next { $_[0]->wrapper->( $_[0]->peek_next_element ) }
125 0     0 1   sub peek_prev { $_[0]->wrapper->( $_[0]->peek_prev_element ) }
126              
127             #===================================
128             sub shift : method {
129             #===================================
130 0     0 1       my $self = shift;
131 0               $self->wrapper->( $self->shift_element );
132             }
133              
134             #===================================
135             sub all {
136             #===================================
137 0     0 1       my $self = shift;
138 0               $self->multi_wrapper->( $self->all_elements(@_) );
139             }
140             #===================================
141             sub slice {
142             #===================================
143 0     0 1       my $self = shift;
144 0               $self->multi_wrapper->( $self->slice_elements(@_) );
145             }
146              
147             #===================================
148             sub first_element {
149             #===================================
150 0     0 0       my $self = shift;
151 0               $self->_i(0);
152 0               $self->get_element(0);
153             }
154              
155             #===================================
156             sub last_element {
157             #===================================
158 0     0 0       my $self = shift;
159 0               my $i = $self->_i( $self->size - 1 );
160 0               $self->get_element($i);
161             }
162              
163             #===================================
164             sub current_element {
165             #===================================
166 0     0 0       my $self = shift;
167 0               my $i = $self->_i;
168 0 0             return undef if $i == -1;
169 0               return $self->get_element($i);
170             }
171              
172             #===================================
173             sub next_element {
174             #===================================
175 0     0 0       my $self = shift;
176 0               my $i = $self->_incr_i;
177 0 0             return undef if $i < 0;
178 0               return $self->get_element($i);
179             }
180              
181             #===================================
182             sub prev_element {
183             #===================================
184 0     0 0       my $self = shift;
185 0               my $i = $self->_decr_i;
186 0 0             return undef if $i < 0;
187 0               return $self->get_element($i);
188             }
189              
190             #===================================
191             sub peek_next_element {
192             #===================================
193 0     0 0       my $self = shift;
194 0               my $i = $self->_i;
195 0               my $raw = $self->next_element;
196 0               $self->_i($i);
197 0               return $raw;
198             }
199              
200             #===================================
201             sub peek_prev_element {
202             #===================================
203 0     0 0       my $self = shift;
204 0               my $i = $self->_i;
205 0               my $raw = $self->prev_element;
206 0               $self->_i($i);
207 0               return $raw;
208             }
209              
210             #===================================
211             sub shift_element {
212             #===================================
213 0     0 0       my $self = shift;
214 0               $self->_i(-1);
215 0               CORE::shift @{ $self->elements };
  0            
216             }
217              
218             #===================================
219             sub all_elements {
220             #===================================
221 0     0 0       my $self = shift;
222 0               $self->_fetch_until( $self->size - 1 );
223 0               @{ $self->elements };
  0            
224             }
225              
226             #===================================
227 0 0   0 1   sub even { my $i = shift->_i; $i < 0 ? undef : !!( $i % 2 ) }
  0            
228 0 0   0 1   sub odd { my $i = shift->_i; $i < 0 ? undef : !( $i % 2 ) }
  0            
229 0 0   0 1   sub parity { my $i = shift->_i; $i < 0 ? undef : $i % 2 ? 'even' : 'odd' }
  0 0          
230 0 0   0 1   sub is_first { my $i = shift->_i; $i < 0 ? undef : $i == 0 }
  0            
231 0 0   0 1   sub is_last { my $i = $_[0]->_i; $i < 0 ? undef : $i == $_[0]->size - 1 }
  0            
232 0     0 1   sub has_next { $_[0]->_i < $_[0]->size - 1 }
233 0 0   0 1   sub has_prev { !!( $_[0]->_i == 0 ? 0 : $_[0]->size ) }
234             #===================================
235              
236             #===================================
237             sub slice_elements {
238             #===================================
239 0     0 0       my $self = shift;
240 0   0           my $first = shift || 0;
241 0   0           my $length = shift || 0;
242 0               my $size = $self->size;
243 0 0             $first = $first + $size if $first < 0;
244 0 0             my $last = $length ? $first + $length - 1 : $size - 1;
245 0 0             if ( $last > $size - 1 ) {
246 0                   $last = $size - 1;
247                 }
248 0               my @slice;
249 0 0             if ( $first < $size ) {
250 0                   $self->_fetch_until($last);
251 0                   my $elements = $self->elements;
252 0                   @slice = @{$elements}[ $first .. $last ];
  0            
253                 }
254 0               return @slice;
255             }
256              
257             #===================================
258             sub as_elements {
259             #===================================
260 0     0 1       my $self = shift;
261 0     0         $self->wrapper( sub { shift() } );
  0            
262 0     0         $self->multi_wrapper( sub {@_} );
  0            
263 0               $self;
264             }
265              
266             #===================================
267       0     sub _fetch_until { }
268             #===================================
269              
270             # TODO: extra methods for iterator
271             #=element C<page()>
272             #
273             # %results = $browse->page($page_no)
274             # %results = $browse->page(page => $page_no, page_size => $rows_per_page)
275             #
276             #Returns a HASH ref with the following keys:
277             #
278             # - total: total number of elements in the list
279             # - page: current page (will be the last available page if $page_no
280             # greated than last_page
281             # - last_page: the last available page
282             # - start_row: the number of the first element (1..$total)
283             # - last_row: the number of the last element
284             # - results: an iterator containing the requested elements
285             #
286             #=cut
287             #
288             ##===================================
289             #sub page {
290             ##===================================
291             # my $self = shift;
292             # my %params
293             # = @_ != 1 ? @_
294             # : ref $_[0] eq ' HASH ' ? %{ $_[0] }
295             # : ( page => $_[0] || 1 );
296             #
297             # my $total = $self->size
298             # or return;
299             #
300             # my $page_size = $params{page_size} || 10;
301             # my $last_page = int( ( $total - 1 ) / $page_size ) + 1;
302             # my $page = make_int( $params{page} );
303             #
304             # $page = 1 if $page < 1;
305             # $page = $last_page if $page > $last_page;
306             #
307             # my $start_index = ( $page - 1 ) * $page_size;
308             # my %search = (
309             # page => $page,
310             # last_page => $last_page,
311             # total => $total,
312             # start_row => $start_index + 1,
313             # end_row => $start_index + $page_size,
314             # page_size => $page_size
315             # );
316             #
317             # $search{end_row} = $total if $total < $search{end_row};
318             # $self->_index($start_index);
319             #
320             # # so next_id gives us the first in the list
321             # $self->prev_id;
322             #
323             # my @ids;
324             # for ( 1 .. $page_size ) {
325             # my $id = $self->next_id || last;
326             # push @ids, $id;
327             # }
328             #
329             # $search{results} = $self->_iterator_class->new(
330             # class => $self->object_class,
331             # ids => \@ids
332             # );
333             #
334             # $search{results}->preload;
335             # return \%search;
336             #}
337             #
338              
339             1;
340              
341             =pod
342            
343             =encoding UTF-8
344            
345             =head1 NAME
346            
347             Elastic::Model::Role::Iterator - A generic iterator role
348            
349             =head1 VERSION
350            
351             version 0.52
352            
353             =head1 SYNOPSIS
354            
355             print "Total: ".$iter->size;
356            
357             while (my $el = $iter->next) {
358             print "Row: ".$iter->index + 1;
359             print "Data: $el";
360             print "More to come..."
361             unless $iter->is_last;
362             }
363            
364             =head1 DESCRIPTION
365            
366             L<Elastic::Model::Role::Iterator> is a generic iterator role which is
367             applied to L<Elastic::Model::Results> and L<Elastic::Model::Results::Scrolled>
368             via L<Elastic::Model::Role::Results>.
369            
370             =head1 ATTRIBUTES
371            
372             =head2 elements
373            
374             \@elements = $iter->elements
375            
376             An array ref containing all of the data structures that we can iterate over.
377            
378             =head2 size
379            
380             $size = $iter->size
381            
382             The number of elements in L</elements>.
383            
384             =head2 wrapper
385            
386             $iter = $iter->wrapper($code_ref);
387             $code_ref = $iter->wrapper();
388            
389             A coderef that wraps all single-element wrapped accessors. Defaults to
390             L</as_elements()>.
391            
392             =head2 multi_wrapper
393            
394             $iter = $iter->multi_wrapper($code_ref);
395             $code_ref = $iter->multi_wrapper();
396            
397             A coderef that wraps all multi-element wrapped accessors. Defaults to
398             L</as_elements()>.
399            
400             =head1 ITERATOR CONTROL
401            
402             =head2 index
403            
404             $index = $iter->index; # index of the current element, or undef
405             $iter->index(0); # set the current element to the first element
406             $iter->index(-1); # set the current element to the last element
407             $iter->index(undef); # resets the iterator, no current element
408            
409             L</index> contains the current index of the iterator. Before you start
410             iterating, it will return undef.
411            
412             =head2 reset
413            
414             $iter->reset;
415            
416             Resets the iterator so that the next call to L</next> will return
417             the first element. B<Note:> any calls to L</shift> means that those
418             elements have been discarded. L</reset> will not reload these.
419            
420             =head1 INFORMATIONAL ACCESSORS
421            
422             =head2 size
423            
424             $size = $iter->size;
425            
426             Returns the number of L</elements>.
427            
428             =head2 even
429            
430             $bool = $iter->even
431            
432             Is the current L</index> even?
433            
434             =head2 odd
435            
436             $bool = $iter->odd
437            
438             Is the current L</index> odd?
439            
440             =head2 parity
441            
442             $parity = $iter->parity
443            
444             Returns C<'odd'> or C<'even'>. Useful for alternating the colour of rows:
445            
446             while ( my $el = $iter->next ) {
447             my $css_class = $el->parity;
448             # display row
449             }
450            
451             =head2 is_first
452            
453             $bool = $iter->is_first
454            
455             Is the L</current> element the first element?
456            
457             =head2 is_last
458            
459             $bool = $iter->is_last
460            
461             Is the L</current> element the last element?
462            
463             =head2 has_next
464            
465             $bool = $iter->has_next
466            
467             Is there a L</next> element?
468            
469             =head2 has_prev
470            
471             $bool = $iter->has_prev
472            
473             Is there a L</prev> element?
474            
475             =head1 WRAPPERS
476            
477             All of the accessors ending in C<_element> or C<_elements> returns the
478             raw data structure stored in L</elements>.
479            
480             The "short" accessors (eg L</first>, L</next>) pass the result of the
481             "long" accessors (eg C<first_element>, C<next_element>) through
482             the L</wrapper> (or L</multi_wrapper> for accessors with multiple return values),
483             allowing the wrapper to transform the raw data in some way.
484            
485             The default for the "short" accessors is just to return the value
486             unchanged.
487            
488             =head2 as_elements()
489            
490             $iter->as_elements()
491            
492             Sets the L</wrapper> and L</multi_wrapper> to return the raw data structures
493             stored in L</elements>.
494            
495             =head1 ELEMENT ACCESSORS
496            
497             All of the accessors below have 2 forms:
498            
499             =over
500            
501             =item *
502            
503             Element, eg C<next_element> which returns the raw element.
504            
505             =item *
506            
507             Short, which passes the raw element through the L</wrapper> or
508             L</multi_wrapper> currently in effect.
509            
510             =back
511            
512             =head2 first
513            
514             $el = $iter->first
515            
516             Returns the first element, and resets the iterator so that a call
517             to L</next> will return the second element. If there is
518             no first element, it returns undef.
519            
520             Also C<first_element>
521            
522             =head2 next
523            
524             $el = $iter->next;
525            
526             Returns the next element, and advances the iterator by one. If there is
527             no next element, it returns undef. If the next element is the last
528             element, then it will work like this:
529            
530             $iter->next; # returns last element
531             $iter->next; # returns undef, and resets iterator
532             $iter->next; # returns first element
533            
534             Also C<next_element>
535            
536             =head2 prev
537            
538             $el = $iter->prev
539            
540             Returns the previous element, and moves the iterator one step in reverse. If
541             there is no previous element, it returns undef. If the previous element is the
542             first element, then it will work like this:
543            
544             $iter->prev; # returns prev element
545             $iter->prev; # returns undef, and resets iterator to end
546             $iter->prev; # returns last element
547            
548             Also C<prev_element>
549            
550             =head2 current
551            
552             $el = $iter->current
553            
554             Returns the current element, or undef
555            
556             Also C<current_element>
557            
558             =head2 last
559            
560             $el = $iter->last
561            
562             Returns the last element, and resets the iterator so that a call
563             to L</next> will return undef, and a second call to
564             L</next> will return the first element If there is
565             no last element, it returns undef.
566            
567             Also C<last_element>
568            
569             =head2 peek_next
570            
571             $el = $iter->peek_next
572            
573             Returns the next element (or undef), but doesn't move the iterator.
574            
575             Also C<peek_next_element>
576            
577             =head2 peek_prev
578            
579             $el = $iter->peek_prev
580            
581             Returns the previous element (or undef), but doesn't move the iterator.
582            
583             Also C<peek_prev_element>
584            
585             =head2 shift
586            
587             $el = $iter->shift
588            
589             Returns the L</first> element and removes it from from the list. L</size>
590             will decrease by 1. Returns undef if there are no more elements.
591            
592             Also C<shift_element>
593            
594             =head2 slice
595            
596             @els = $iter->slice($offset,$length);
597            
598             Returns a list of (max) C<$length> elements, starting at C<$offset> (which
599             is zero-based):
600            
601             $iter->slice(); # all elements;
602             $iter->slice(5); # elements 5..size
603             $iter->slice(-5); # elements size-5..size
604             $iter->slice(0,10); # elements 0..9
605             $iter->slice(5,10); # elements 5..14
606            
607             If your iterator only contains 5 elements:
608            
609             $iter->slice(3,10); # elements 3..4
610             $iter->slice(10,10); # an empty list
611            
612             Also C<slice_elements>
613            
614             =head2 all
615            
616             @els = $iter->all
617            
618             Returns all L</elements> as a list.
619            
620             Also C<all_elements>
621            
622             =head1 AUTHOR
623            
624             Clinton Gormley <drtech@cpan.org>
625            
626             =head1 COPYRIGHT AND LICENSE
627            
628             This software is copyright (c) 2015 by Clinton Gormley.
629            
630             This is free software; you can redistribute it and/or modify it under
631             the same terms as the Perl 5 programming language system itself.
632            
633             =cut
634              
635             __END__
636            
637             # ABSTRACT: A generic iterator role
638            
639