File Coverage

blib/lib/Data/Importer.pm
Criterion Covered Total %
statement 58 67 86.5
branch 8 14 57.1
condition 1 3 33.3
subroutine 17 20 85.0
pod 11 11 100.0
total 95 115 82.6


line stmt bran cond sub pod time code
1             #
2             # This file is part of Data-Importer
3             #
4             # This software is copyright (c) 2014 by Kaare Rasmussen.
5             #
6             # This is free software; you can redistribute it and/or modify it under
7             # the same terms as the Perl 5 programming language system itself.
8             #
9             package Data::Importer;
10             $Data::Importer::VERSION = '0.006';
11 1     1   1123965 use 5.010;
  1         3  
  1         34  
12 1     1   5 use namespace::autoclean;
  1         3  
  1         5  
13 1     1   56 use Moose;
  1         1  
  1         6  
14 1     1   5669 use Moose::Util::TypeConstraints;
  1         2  
  1         9  
15 1     1   2964 use DateTime;
  1         1489967  
  1         48  
16 1     1   964 use DateTime::Format::Pg;
  1         35005  
  1         11  
17              
18             with 'MooseX::Traits';
19              
20             # ABSTRACT: Framework to import row-based (spreadsheet-, csv-) files into a database
21              
22             =head1 DESCRIPTION
23              
24             Base class for handling the import of the spreadsheet file
25              
26             Subclass this.
27              
28             =head1 SYNOPSIS
29              
30             # Your Importer class
31             package My::Importer;
32              
33             use Moose;
34              
35             extends 'Data::Importer';
36              
37             sub mandatory_columns {
38             return [qw/name description price/];
39             }
40              
41             sub validate_row {
42             my ($self, $row, $lineno) = @_;
43             # XXX validation
44             # return $self->add_error("Some error at $lineno") if some condition;
45             # return $self->add_warning("Some warning at $lineno") if some condition;
46             $self->add_row($row);
47             }
48              
49             sub import_row {
50             my ($self, $row) = @_;
51             $schema->resultset('Table')->create($row) or die;
52             }
53              
54             # Import the file like this
55              
56             my $import = My::Importer->new(
57             schema => $schema,
58             file_name => $file_name,
59             );
60             $import->do_work;
61              
62             =head1 ATTTRIBUTES
63              
64             =head2 schema
65              
66             Yes, we use DBIx::Class. Send your schema.
67              
68             =cut
69              
70             has 'schema' => (
71             isa => 'DBIx::Class',
72             is => 'ro'
73             );
74              
75             =head2 dry_run
76              
77             Set to true if this is only a test
78              
79             =cut
80              
81             has 'dry_run' => (
82             isa => 'Bool',
83             is => 'ro',
84             default => 0
85             );
86              
87             =head2 file_name
88              
89             The name of the spreadsheet
90              
91             =cut
92              
93             has file_name => (
94             is => 'ro',
95             isa => 'Str',
96             );
97              
98             =head2 encoding
99              
100             The encoding of the spreadsheet
101              
102             =cut
103              
104             has encoding => (
105             is => 'ro',
106             isa => 'Str',
107             default => 'utf8',
108             lazy => 1,
109             );
110              
111             =head2 import_type
112              
113             Three formats are supported, csv, xls, ods
114              
115             =cut
116              
117             enum 'import_types', [qw(csv ods xls)];
118              
119             has 'import_type' => (
120             is => 'ro',
121             isa => 'import_types',
122             lazy_build => 1,
123             );
124              
125             =head2 mandatory
126              
127             Required input columns
128              
129             =cut
130              
131             has 'mandatory' => (
132             is => 'ro',
133             isa => 'ArrayRef',
134             lazy_build => 1,
135             builder => 'mandatory_columns'
136             );
137              
138             =head2 optional
139              
140             Required input columns
141              
142             =cut
143              
144             has 'optional' => (
145             is => 'ro',
146             isa => 'ArrayRef',
147             lazy_build => 1,
148             builder => 'optional_columns'
149             );
150              
151             =head1 "PRIVATE" ATTRIBUTES
152              
153             =head2 import_iterator
154              
155             An iterator for reading the import file
156              
157             =cut
158              
159             has 'import_iterator' => (
160             is => 'ro',
161             lazy_build => 1,
162             );
163              
164             =head2 rows
165              
166             An arrayref w/ the rows to be inserted / updated
167              
168             =cut
169              
170             has 'rows' => (
171             is => 'ro',
172             isa => 'ArrayRef',
173             traits => [qw/Array/],
174             handles => {
175             add_row => 'push',
176             },
177             default => sub { [] },
178             );
179              
180             =head2 warnings
181              
182             An arrayref w/ all the warnings picked up during processing
183              
184             These methods are associated with warnings:
185              
186             =head3 all_warnings
187              
188             Returns all elements
189              
190             =head3 add_warning
191              
192             Add a warning
193              
194             =head3 join_warnings
195              
196             Join all warnings
197              
198             =head3 count_warnings
199              
200             Returns the number of warnings
201              
202             =head3 has_warnings
203              
204             Returns true if there are warnings
205              
206             =head3 has_no_warnings
207              
208             Returns true if there isn't any warning
209              
210             =head3 clear_warnings
211              
212             Clear out all warnings
213              
214             =cut
215              
216             has 'warnings' => (
217             traits => ['Array'],
218             is => 'ro',
219             isa => 'ArrayRef[Str]',
220             default => sub { [] },
221             handles => {
222             all_warnings => 'elements',
223             add_warning => 'push',
224             join_warnings => 'join',
225             count_warnings => 'count',
226             has_warnings => 'count',
227             has_no_warnings => 'is_empty',
228             clear_warnings => 'clear',
229             },
230             );
231              
232             =head2 errors
233              
234             An arrayref w/ all the errors picked up during processing
235              
236             These methods are associated with errors:
237              
238             =head3 all_errors
239              
240             Returns all elements
241              
242             =head3 add_error
243              
244             Add a error
245              
246             =head3 join_errors
247              
248             Join all errors
249              
250             =head3 count_errors
251              
252             Returns the number of errors
253              
254             =head3 has_errors
255              
256             Returns true if there are errors
257              
258             =head3 has_no_errors
259              
260             Returns true if there isn't any error
261              
262             =head3 clear_errors
263              
264             Clear out all errors
265              
266             =cut
267              
268             has 'errors' => (
269             traits => ['Array'],
270             is => 'ro',
271             isa => 'ArrayRef[Str]',
272             default => sub { [] },
273             handles => {
274             all_errors => 'elements',
275             add_error => 'push',
276             join_errors => 'join',
277             count_errors => 'count',
278             has_errors => 'count',
279             has_no_errors => 'is_empty',
280             clear_errors => 'clear',
281             },
282             );
283              
284             =head2 timestamp
285              
286             Time of the import run.
287              
288             =cut
289              
290             has 'timestamp' => (
291             isa => 'Str',
292             is => 'rw',
293             lazy => 1,
294             default => sub {
295             return DateTime::Format::Pg->format_datetime(DateTime->now);
296             },
297             );
298              
299             =head1 METHODS
300              
301             =cut
302              
303             sub _build_import_type {
304 2     2   5 my $self = shift;
305 2         247 my $file_name = $self->file_name;
306 2 50       17 $file_name =~ /\.(\w+)$/ || return;
307              
308 2         85 return lc $1;
309             }
310              
311             sub _build_import_iterator {
312 2     2   5 my $self = shift;
313 2         89 my $import_type = ucfirst $self->import_type;
314 2         9 my $classname = "Data::Importer::Iterator::$import_type";
315 2 100       225 eval "require $classname" or die $@;
316 1         388 return $classname->new(
317             file_name => $self->file_name,
318             mandatory => $self->mandatory,
319             optional => $self->optional,
320             encoding => $self->encoding,
321             );
322             }
323              
324             =head2 mandatory_columns
325              
326             Builds the mandatory attribute, which gives an arrayref with the required columns.
327              
328             =cut
329              
330 1     1 1 30 sub mandatory_columns { [] }
331              
332             =head2 optional_columns
333              
334             Builds the optional attribute, which gives an arrayref with the optional columns.
335              
336             =cut
337              
338 1     1 1 27 sub optional_columns { [] }
339              
340             =head2 do_work
341              
342             Import a file
343              
344             =cut
345              
346             sub do_work {
347 2     2 1 434 my $self = shift;
348 2         16 $self->validate;
349 1 50 33     140 $self->import_run unless $self->dry_run or $self->has_errors;
350 1         15 $self->report;
351 1         13 return 1;
352             }
353              
354             =head2 validate
355              
356             Validates the import file
357              
358             =cut
359              
360             sub validate {
361 2     2 1 5 my ($self) = @_;
362 2         95 my $iterator = $self->import_iterator;
363              
364 1         4 while (my $row = $iterator->next) {
365 62         42198 my $row_res = $self->validate_row($row, $iterator->lineno);
366             }
367             }
368              
369             =head2 validate_row
370              
371             Handle each individual row.
372              
373             This has to be written in the subclass
374              
375             =cut
376              
377             sub validate_row {
378 0     0 1 0 my ($self, $row, $lineno) = @_;
379 0         0 die "You have to provide your own validate_row";
380             }
381              
382             =head2 import_run
383              
384             Imports the nodes after validate has been run
385              
386             =cut
387              
388             sub import_run {
389 1     1 1 3 my ($self) = @_;
390 1 50       36 die "Trying to run import even with errors!" if $self->has_errors;
391              
392 1         32 my $schema = $self->schema;
393             my $transaction = sub {
394 1     1   1654978 $self->import_transaction;
395 1         1613 return;
396 1         6 };
397 1         1 my $rs;
398 1         3 eval {
399 1         19 $rs = $schema->txn_do($transaction);
400             };
401 1 50       738662 if ($@) {
402 0         0 $self->import_failed($@);
403             } else {
404 1         41 $self->import_succeeded;
405             }
406             }
407              
408             =head2 import_transaction
409              
410             Called inside the transaction block.
411              
412             This is the method to add method modifiers to if processing before or after the
413             row import loop is necessary.
414              
415             =cut
416              
417             sub import_transaction {
418 1     1 1 3 my $self = shift;
419 1         41 my $schema = $self->schema;
420 1         3 for my $row (@{ $self->rows }) {
  1         38  
421 62         337647 $self->import_row($row);
422             }
423             }
424              
425             =head2 import_row
426              
427             Handle each individual row.
428              
429             This has to be written in the subclass
430              
431             =cut
432              
433             sub import_row {
434 0     0 1 0 my ($self, $row, $lineno) = @_;
435 0         0 die "You have to provide your own import_row";
436             }
437              
438             =head2 import_failed
439              
440             Called if the import failed for some reason
441              
442             =cut
443              
444             sub import_failed {
445 0     0 1 0 my ($self, $error) = @_;
446 0         0 die "Database error: $error \n";
447             }
448              
449             =head2 import_succeeded
450              
451             Called after the import has finished succesfully
452              
453             =cut
454              
455 1     1 1 11 sub import_succeeded { }
456              
457             =head2 report
458              
459             Make a report of what happened
460              
461             =cut
462              
463             sub report {
464 1     1 1 5 my ($self) = @_;
465 1 50       126 warn 'Warnings: ' . join("\n", @{ $self->warnings }) if $self->has_warnings;
  0         0  
466 1 50       58 warn 'Errors ' . join("\n", @{ $self->errors }) if $self->has_errors;
  0            
467             }
468              
469             __PACKAGE__->meta->make_immutable;
470              
471             #
472             # This file is part of Data-Importer
473             #
474             # This software is copyright (c) 2014 by Kaare Rasmussen.
475             #
476             # This is free software; you can redistribute it and/or modify it under
477             # the same terms as the Perl 5 programming language system itself.
478             #
479              
480             __END__