active_importer 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +21 -0
- data/lib/active_importer/base.rb +28 -15
- data/lib/active_importer/version.rb +1 -1
- data/spec/active_importer/base_spec.rb +20 -3
- data/spec/stubs/employee.rb +6 -1
- metadata +4 -4
data/README.md
CHANGED
@@ -48,6 +48,27 @@ columns declared. Any extra columns are ignored. Any errors while processing
|
|
48
48
|
the data file does not interrupt the whole process. Instead, errors are
|
49
49
|
notified via some callbacks defined in the importer (see below).
|
50
50
|
|
51
|
+
### Supported formats
|
52
|
+
|
53
|
+
This library currently supports reading from most spreadsheet formats, thanks
|
54
|
+
to the wonderfull [roo](https://github.com/Empact/roo) gem. Specifically, the
|
55
|
+
following formats are supported:
|
56
|
+
|
57
|
+
* OpenOffice
|
58
|
+
* Excel
|
59
|
+
* Google spreadsheets
|
60
|
+
* Excelx
|
61
|
+
* LibreOffice
|
62
|
+
* CSV
|
63
|
+
|
64
|
+
The spreadsheet contents are scanned, row by row, until a row is found that
|
65
|
+
matches the expect header column, which should contain header cells for all the
|
66
|
+
columns declared in the importer. If no such row is found, the spreadsheet
|
67
|
+
processing fails without importing any data.
|
68
|
+
|
69
|
+
If the header row is found, data is scanned from the next row on, until the end
|
70
|
+
of the spreadsheet.
|
71
|
+
|
51
72
|
### Callbacks
|
52
73
|
|
53
74
|
TODO: Document callbacks
|
data/lib/active_importer/base.rb
CHANGED
@@ -26,7 +26,7 @@ module ActiveImporter
|
|
26
26
|
self.class.model_class
|
27
27
|
end
|
28
28
|
|
29
|
-
def self.column(title, field, &block)
|
29
|
+
def self.column(title, field = nil, &block)
|
30
30
|
title = title.strip
|
31
31
|
if columns[title]
|
32
32
|
raise "Duplicate importer column '#{title}'"
|
@@ -52,10 +52,9 @@ module ActiveImporter
|
|
52
52
|
@context = options.delete(:context)
|
53
53
|
|
54
54
|
@book = Roo::Spreadsheet.open(file, options)
|
55
|
-
|
56
|
-
check_header
|
55
|
+
load_header
|
57
56
|
|
58
|
-
@data_row_indices = (
|
57
|
+
@data_row_indices = ((@header_index+1)..@book.last_row)
|
59
58
|
@row_count = @data_row_indices.count
|
60
59
|
rescue => e
|
61
60
|
@book = @header = nil
|
@@ -111,30 +110,44 @@ module ActiveImporter
|
|
111
110
|
self.class.columns
|
112
111
|
end
|
113
112
|
|
114
|
-
def
|
115
|
-
|
116
|
-
|
113
|
+
def find_header_index
|
114
|
+
(1..@book.last_row).each do |index|
|
115
|
+
row = @book.row(index).map(&:strip)
|
116
|
+
return index if columns.keys.all? { |item| row.include?(item) }
|
117
|
+
end
|
118
|
+
return nil
|
119
|
+
end
|
120
|
+
|
121
|
+
def load_header
|
122
|
+
@header_index = find_header_index
|
123
|
+
if @header_index
|
124
|
+
@header = @book.row(@header_index).map(&:strip)
|
125
|
+
else
|
117
126
|
raise 'Spreadsheet does not contain all the expected columns'
|
118
127
|
end
|
119
128
|
end
|
120
129
|
|
121
130
|
def import_row
|
122
|
-
|
123
|
-
|
124
|
-
|
131
|
+
begin
|
132
|
+
@model = fetch_model
|
133
|
+
build_model
|
134
|
+
model.save!
|
135
|
+
rescue => e
|
136
|
+
@row_errors << { row_index: row_index, error_message: e.message }
|
137
|
+
row_error(e.message)
|
138
|
+
return false
|
139
|
+
end
|
125
140
|
row_success
|
126
|
-
|
127
|
-
@row_errors << { row_index: row_index, error_message: e.message }
|
128
|
-
row_error(e.message)
|
141
|
+
true
|
129
142
|
end
|
130
143
|
|
131
144
|
def build_model
|
132
145
|
row.each_pair do |key, value|
|
133
146
|
column_def = columns[key]
|
134
|
-
next if column_def.nil?
|
147
|
+
next if column_def.nil? || column_def[:field_name].nil?
|
135
148
|
field_name = column_def[:field_name]
|
136
149
|
transform = column_def[:transform]
|
137
|
-
value =
|
150
|
+
value = self.instance_exec(value, &transform) if transform
|
138
151
|
model[field_name] = value
|
139
152
|
end
|
140
153
|
hook
|
@@ -4,7 +4,7 @@ require 'stubs/employee'
|
|
4
4
|
describe ActiveImporter::Base do
|
5
5
|
let(:spreadsheet_data) do
|
6
6
|
[
|
7
|
-
[' Name ', 'Birth Date', 'Department'],
|
7
|
+
[' Name ', 'Birth Date', 'Department', 'Manager'],
|
8
8
|
['John Doe', '2013-10-25', 'IT'],
|
9
9
|
['Jane Doe', '2013-10-26', 'Sales'],
|
10
10
|
]
|
@@ -36,7 +36,7 @@ describe ActiveImporter::Base do
|
|
36
36
|
context 'when there are rows with errors' do
|
37
37
|
let(:spreadsheet_data) do
|
38
38
|
[
|
39
|
-
['Name', 'Birth Date', 'Department'],
|
39
|
+
['Name', 'Birth Date', 'Department', 'Manager'],
|
40
40
|
['John Doe', '2013-10-25', 'IT'],
|
41
41
|
['Invalid', '2013-10-24', 'Management'],
|
42
42
|
['Invalid', '2013-10-24', 'Accounting'],
|
@@ -64,7 +64,7 @@ describe ActiveImporter::Base do
|
|
64
64
|
context 'when the import fails' do
|
65
65
|
let(:spreadsheet_data) do
|
66
66
|
[
|
67
|
-
['Name', 'Birth Date'],
|
67
|
+
['Name', 'Birth Date', 'Manager'],
|
68
68
|
['John Doe', '2013-10-25'],
|
69
69
|
['Jane Doe', '2013-10-26'],
|
70
70
|
]
|
@@ -76,6 +76,23 @@ describe ActiveImporter::Base do
|
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
79
|
+
context 'when header row is not the first one' do
|
80
|
+
let(:spreadsheet_data) do
|
81
|
+
[
|
82
|
+
[],
|
83
|
+
['List of employees', '', 'Company Name'],
|
84
|
+
['Ordered by', 'Birth Date'],
|
85
|
+
['Name', 'Department', 'Birth Date', 'Manager'],
|
86
|
+
['John Doe', 'IT', '2013-10-25'],
|
87
|
+
['Jane Doe', 'Sales', '2013-10-26'],
|
88
|
+
]
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'smartly skips any rows before the header' do
|
92
|
+
expect { EmployeeImporter.import('/dummy/file') }.to change(Employee, :count).by(2)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
79
96
|
describe '.fetch_model' do
|
80
97
|
let(:model) { Employee.new }
|
81
98
|
|
data/spec/stubs/employee.rb
CHANGED
@@ -14,7 +14,12 @@ class EmployeeImporter < ActiveImporter::Base
|
|
14
14
|
|
15
15
|
column 'Name', :name
|
16
16
|
column 'Birth Date', :birth_date
|
17
|
+
column 'Manager'
|
17
18
|
column ' Department ', :department_id do |value|
|
18
|
-
value
|
19
|
+
find_department(value)
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_department(name)
|
23
|
+
name.length # Quick dummy way to get an integer out of a string
|
19
24
|
end
|
20
25
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_importer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-11-
|
12
|
+
date: 2013-11-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: roo
|
@@ -112,7 +112,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
112
112
|
version: '0'
|
113
113
|
segments:
|
114
114
|
- 0
|
115
|
-
hash:
|
115
|
+
hash: 3421867451760096133
|
116
116
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
117
|
none: false
|
118
118
|
requirements:
|
@@ -121,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
121
|
version: '0'
|
122
122
|
segments:
|
123
123
|
- 0
|
124
|
-
hash:
|
124
|
+
hash: 3421867451760096133
|
125
125
|
requirements: []
|
126
126
|
rubyforge_project:
|
127
127
|
rubygems_version: 1.8.23
|