Iterator::BreakOn
Este módulo Perl permite crear rupturas de control en iteradores de datos, especialmente aquellos que trabajan con DBI ó alguno de sus derivados (Class::DBI, DBIx::Class, ... ).
1 use Iterator::BreakOn; 2 3 my $datasource = get_generic_data_source(); 4 5 my $iter = Iterator::BreakOn->new( 6 datasource => $datasource, 7 break_before => [ qw(location) ], 8 break_after => [ qw(zipcode) ], 9 on_last_item => sub { print "Finnished !"; }, 10 ); 11 12 # 13 # There are three uses modes: 14 # 15 # Fully automatic mode: useless if not defined code for breaks 16 $iter->run(); 17 18 # Semi-automatic mode: get only the item 19 while (my $data_item = $iter->next()) { 20 # do something ... 21 22 } 23 24 # Manual mode: get every event 25 while (my $event = $iter->next_event()) { 26 if ($event eq 'before_location') { 27 # do something before a new location comes 28 29 } 30 elsif ($event eq 'after_zipcode')) { 31 # do something after the last zipcode reached 32 33 } 34 elsif ($event eq 'next_item' ) { 35 # get the item (including the first and last items) 36 my $data = $iter->next(); 37 38 # and do something whit him 39 40 } 41 elsif ($event eq 'last_item') { 42 # and do something when the end of data reached 43 44 } 45 } # end while
Se asume que la fuente de datos está ordenada -al menos- por los campos de ruptura, puesto que no se comprueba si éstos están en un orden concreto, simplemente si cambian su valor con respecto al anterior registro.
Como casos especiales, claro está, aparece el primer registro, el último y una lista vacía, aunque la dinámica de creación de eventos se mantiene.
Creación de objetos
Existe el tradicional método new
para crear un objeto de este tipo con los
siguientes atributos:
- datasource: referencia a un objeto con un método
next
disponible para recibir uno a uno los datos (obligatorio). - getmethod: nombre del método para recuperar valores individuales de
cada elemento leído. De esto se deduce que cada llamada al método
next
anteriormente mencionado debe retornar un objeto con dicha capacidad. El valor predeterminado esget
en el móduloIterator::BreakOn::Base
yget_column
enIterator::BreakOn
. - private: Permite guardar una referencia a algo que será recuperada
mediante el método
Iterator::BreakOn::private()
en cualquier parte y momento. - break_before: Lista de campos sobre los que generar un evento antes de cambiar su valor.
- break_after: Lista de campos sobre los que generar un evento después de que cambie su valor.
- on_first: Referencia a código para ejecutar antes del primer elemento leído.
- on_every: Referencia a código para ejecutar con cada elemento.
- on_last: Referencia a código para ejecutar tras el último elemento.
En el caso de break_before
y break_after
la lista de campos puede
consistir en una simple lista de nombres ó todos ó sólo algunos de ellos
pueden hacer referencia a código.
Ejemplo
1 my $iter = Iterator::BreakOn->new( 2 break_before => [ 3 'custom', 4 'article' => \&before_change_article, 5 'product' 6 ], 7 );
Todas las funciones proporcionadas reciben una referencia al objeto iterador
como primer parámetro, a excepción de los eventos after
y before
, en cuyo
caso se añaden a éste el nombre del campo y el valor del mismo que ha
provocado el evento.
Orden de los eventos
El orden en el que se producen los eventos es el siguiente:
- La primera vez que se lee de la fuente de datos:
- Generamos un único evento on_first.
- Generamos un evento before_ por cada campo listado.
- Lee un registro:
- Si es el último:
- Generamos un evento after_ por cada campo listado desde el más interior al más exterior.
- Generamos un único evento on_last.
- Si no es el último
- Si ha cambiado alguno de los valores:
- Generamos un evento after por cada campo desde el que provoca la ruptura has el último, pero inviertiendo el orden de ejecución como mencionamos más arriba.
- Generamos un evento before por cada campo desde el que provoca la rupta hasta el último.
- Generamos un evento on_every con los datos del registro.
- Si ha cambiado alguno de los valores:
- Si es el último:
Modos de funcionamiento
Existen tres modos de funcionamiento básico para recorrer el iterador de datos. Cada uno de los cuales puede necesitar más o menos referencias a código externo, dependiendo de lo que queramos conseguir.
Modo automático
1 $iter->run()
La llamada a este método provoca la lectura ininterrumpida de la fuente de datos, y el procesado de todos y cada uno de los eventos. Aquellos que no tengan código asociado serán ignorados completamente.
Modo semiautomático
1 while (my $data_item = $iter->next()) { 2 # do something ... 3 4 }
El método next
retorna el siguiente elemento de la fuente de datos,
procesando automáticamente cualquier evento distinto de on_every
. Idóneo
para centrarse únicamente en los datos.
Modo manual
1 while (my $event = $iter->next_event()) { 2 if ($event eq 'before_location') { 3 # do something before a new location comes 4 5 } 6 elsif ($event eq 'after_zipcode')) { 7 # do something after the last zipcode reached 8 9 } 10 elsif ($event eq 'next_item' ) { 11 # get the item (including the first and last items) 12 my $data = $iter->next(); 13 14 # and do something whit him 15 16 } 17 elsif ($event eq 'last_item') { 18 # and do something when the end of data reached 19 20 } 21 } # end while
El método next_event
permite tener constancia de cada uno de los eventos que
se crean al leer la fuente de datos. Pensando para cuando se prefiere un
control absoluto sobre el proceso.
Ejemplos de uso
1 #!/usr/bin/perl 2 3 use lib qw(lib examples); 4 use datasource; 5 use Iterator::BreakOn; 6 7 my $resultset = datasource->new(); 8 9 # 10 # Data source is ordered by location, zipcode and name 11 # 12 my $iter = Iterator::BreakOn->new( 13 datasource => $resultset, 14 private => { 15 zipcode_totals => 0, 16 location_totals => 0, 17 }, 18 break_before => [ 19 location => \&before, 20 zipcode => \&before, 21 ], 22 break_after => [ 23 location => \&after, 24 zipcode => \&after, 25 ], 26 on_every => \&on_every, 27 ); 28 29 $iter->run(); 30 31 sub before { 32 my ($self, $field, $value) = @_; 33 my $data = $self->private(); 34 35 if ($field eq 'location') { 36 print sprintf("%s: %s\n", $field, $value); 37 $data->{location_totals} = 0; 38 } 39 elsif ($field eq 'zipcode') { 40 $data->{zipcode_totals} = 0; 41 } 42 } 43 44 sub after { 45 my ($self, $field, $value) = @_; 46 my $data = $self->private(); 47 48 if ($field eq 'zipcode') { 49 print sprintf("\n\t\tTotals for zipcode %s: %.2f\n\n", $value, 50 $data->{zipcode_totals}); 51 $data->{location_totals} += $data->{zipcode_totals}; 52 $data->{zipcode_totals} = 0; 53 } 54 elsif ($field eq 'location') { 55 print sprintf("\n\tTotals for location %s: %.2f\n\n", $value, 56 $data->{location_totals}); 57 $data->{location_totals} = 0; 58 } 59 60 return; 61 } 62 63 sub on_every { 64 my $self = shift; 65 my %values = $self->item->getall(); 66 my $data = $self->private(); 67 68 print sprintf("\t%s\t%-30s\t%.2f\n", $values{zipcode}, $values{name}, 69 $values{amount} ); 70 71 $data->{zipcode_totals} += $values{amount}; 72 } 73 74
Existe un módulo complementario que crea y mantiene la fuente de datos llamado datasource.pm.
Descargas
Este software está disponible en el CPAN como Iterator::BreakOn y en mi repositorio Debian.
Y además, las fuentes pueden descargarse de: