package Data::AnyXfer;
use Modern::Perl;
use Moo;
use MooX::Types::MooseLike::Base qw(:all);
use File::Temp qw/ tempdir /;
use Clone qw/ clone /;
use Path::Class qw( dir );
use Data::AnyXfer::Log4perl qw/ get_logger /;
our $VERSION = '0.1';
=head1 NAME
Data::AnyXfer - data transfer base class
=head1 DESCRIPTION
This is a base class for data transfers. It does nothing on it's own
except log calls to methods for tracing.
=head1 ATTRIBUTES
=head2 C
This is an optional callback on the hashref returned by
L. It should be a subroutine that takes a
L object and a hash reference as
arguments, e.g.
use Intranet::MGReports::Import::Lettings;
sub debug {
my ( $import, $data ) = @_;
$import->log->debug( Dumper($data) );
return 1;
}
my $import = Intranet::MGReports::Import:Lettings->new(
callback => \&debug, #
);
If the callback returns a false value, then the run will stop.
Note that the callback is passed a copy of the record, and any
modifications will not be saved.
Generally this will only be used for testing and debugging.
=cut
has 'callback' => (
is => 'ro',
isa => Maybe[CodeRef],
);
=head2 C
This is an optional L object. It defaults to
the logger returned by L, and can be used
for logging events during a script, e.g.
$self->log->warn("Fnorjd!");
=cut
has 'log' => (
is => 'ro',
isa => InstanceOf['Log::Log4perl::Logger'],
lazy => 1,
default => sub { get_logger() },
);
=head1 METHODS
=head2 test
Data::AnyXfer->test(1);
Sets or retreives the testing flag to trigger different behaviour.
=cut
my $DATA_ANYXFER_TEST = 0;
sub test {
my ($self, $value) = @_;
return defined $value ? $DATA_ANYXFER_TEST = $value : $DATA_ANYXFER_TEST;
}
=head2 tmp_dir
my $tmp_dir = Data::AnyXfer->tmp_dir;
say $tmp_dir; # --> /tmp/tmp_dir-user-pid-random_chars
Or...
my $tmp_dir = Data::AnyXfer->tmp_dir({
name => 'my-project',
cleanup => 0, # don't delete dir
});
say $tmp_dir' # --> /tmp/my-project-user-pid-random_chars
Returns a L object representing a recently created
directory. The directory will have the user and pid embedded in it and will
be deleted when the program exits, unless 'cleanup' argument set to false
or the C environment variable is set to true.
=cut
sub tmp_dir {
my ( $class, $args ) = @_;
$args->{name} ||= 'tmp_dir';
$args->{cleanup} = 1 unless defined $args->{cleanup};
$args->{cleanup} = 0 if $ENV{TMP_NO_CLEANUP};
my $template = sprintf( "%s-%s-%s-XXXXXX", $args->{name}, $ENV{USER}, $$ );
my $tmp = tempdir( $template, CLEANUP => $args->{cleanup}, TMPDIR => 1, );
return dir($tmp);
}
=head2 C
$self->run();
This method populates the data needed to run reports, by doing the following:
It runs the L method. If that returns false value, it
stops. Otherwise, it calls the L method until it returns
false.
If L returns an object, then it calls the L
method on that object, expecting a hashref in return.
If there is a L defined, that is called with the hashref.
If the hashref is defined and has keys, then it calls the L
method to save the data.
=cut
sub run {
my ($self) = @_;
my $log = $self->log;
if ( $self->initialize ) {
my $cb = $self->callback;
while ( my $res = $self->fetch_next ) {
if ( my $rec = $self->transform($res) ) {
$self->store($rec)
or $log->logdie("store failed");
if ( $cb && !$cb->( $self, clone $rec ) ) {
$log->trace("callback returned false")
if $log->is_trace;
last;
}
}
}
$self->finalize;
} else {
$log->trace("initialize returned false")
if $log->is_trace;
}
return 1;
}
=head2 C
if ($self->initialize) { ... }
This method initializes the system for the data transfer. This may
involve opening files, connecting to databases, initialising objects,
etc.
It returns false on failure. Any wrappers around this method should
check for false in the original method before continuing, e.g.
around 'initialize' => sub {
my ($orig, $self) = @_;
$self->$orig() or return;
...
};
=cut
sub initialize {
my ($self) = @_;
my $log = $self->log;
$log->trace( ( caller(0) )[3] =~ m/::(\w+)$/ ) if $log->is_trace;
return 1;
}
=head2 C
while (my $res = $self->fetch_next) { ... }
This method provides an iterator for the data source. It should
return an object that can be processed by the L method, or
C when there is no more data.
An example iterator for a L might be
around 'fetch_next' => sub {
my ( $orig, $self ) = @_;
$self->$orig or return;
$self->rs->next;
};
=cut
sub fetch_next {
my ($self) = @_;
my $log = $self->log;
$log->trace( ( caller(0) )[3] =~ m/::(\w+)$/ ) if $log->is_trace;
return 1;
}
=head2 C
my $rec = $self->transform($res);
This method should transform the object returns by L into a
hash reference.
The transform method should return either a hash reference, or
C.
If C is returned, the L and L methods will
not be called.
=cut
sub transform {
my ($self) = @_;
my $log = $self->log;
$log->trace( ( caller(0) )[3] =~ m/::(\w+)$/ ) if $log->is_trace;
return {};
}
=head2 C
$self->store($rec);
This method stores the record returned by L. It returns a
false value on failure.
=cut
sub store {
my ( $self, $rec ) = @_;
my $log = $self->log;
$log->trace( ( caller(0) )[3] =~ m/::(\w+)$/ ) if $log->is_trace;
return 1;
}
=head2 C
$self->finalize();
This method finalizes any data after all records have been saved. It
returns a false value on faulure.
=cut
sub finalize {
my ($self) = @_;
my $log = $self->log;
$log->trace( ( caller(0) )[3] =~ m/::(\w+)$/ ) if $log->is_trace;
return 1;
}
use namespace::autoclean;
__PACKAGE__->meta->make_immutable;
1;
=head1 COPYRIGHT
This software is copyright (c) 2019, Anthony Lucas.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
=cut