active_importer 0.1.1 → 0.2.0
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 +39 -2
- data/lib/active_importer/base.rb +21 -6
- data/lib/active_importer/version.rb +1 -1
- data/spec/active_importer/base_spec.rb +6 -7
- data/spec/stubs/data_model.rb +14 -4
- data/spec/stubs/employee.rb +1 -1
- metadata +4 -4
data/README.md
CHANGED
@@ -80,12 +80,16 @@ class EmployeeImporter < ActiveImporter::Base
|
|
80
80
|
|
81
81
|
attr_reader :row_count
|
82
82
|
|
83
|
-
column 'First name'
|
84
|
-
column 'Last name'
|
83
|
+
column 'First name'
|
84
|
+
column 'Last name'
|
85
85
|
column 'Department', :department do |department_name|
|
86
86
|
Department.find_by(name: department_name)
|
87
87
|
end
|
88
88
|
|
89
|
+
on :row_processing do
|
90
|
+
model.full_name = [row['First name'], row['Last name']].join(' ')
|
91
|
+
end
|
92
|
+
|
89
93
|
on :import_started do
|
90
94
|
@row_count = 0
|
91
95
|
end
|
@@ -117,6 +121,8 @@ The supported events are:
|
|
117
121
|
event is fired by an importer, none of its other events are ever fired.
|
118
122
|
- **import_started:** Fired once at the beginning of the data processing,
|
119
123
|
before the first row is processed.
|
124
|
+
- **row_processing:** Fired while the row is being processed to be imported
|
125
|
+
into a model instance.
|
120
126
|
- **row_processed:** Fired once for each row that has been processed,
|
121
127
|
regardless of whether it resulted in success or error.
|
122
128
|
- **row_success:** Fired once for each row that was imported successfully into
|
@@ -132,6 +138,37 @@ all the importer attributes and instance variables. Error-related events
|
|
132
138
|
(`:import_failed` and `:row_error`) pass to the blocks the instance of the
|
133
139
|
exception that provoked the error condition.
|
134
140
|
|
141
|
+
Additionally, all the `row_*` events have access to the `row` and `model`
|
142
|
+
variables, which reference the spreadsheet row being processed, and the model
|
143
|
+
object where the row data is being stored, respectively. This feature is
|
144
|
+
specifically useful for the `:row_processing` event handler, which is triggered
|
145
|
+
while a row is being processed, and before the corresponding data model is
|
146
|
+
saved. This allows to define any complex data-import logic that cannot be
|
147
|
+
expressed in terms of mapping a column to a data field.
|
148
|
+
|
149
|
+
### Selecting the model instance to import into
|
150
|
+
|
151
|
+
By default, the importer will attempt to generate a new model instance per row
|
152
|
+
processed. The importer can be instructed to update records instead, if they
|
153
|
+
already exist, instead of always attempting to generate a new one.
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
class EmployeeImporter
|
157
|
+
imports Employee
|
158
|
+
|
159
|
+
fetch_model do
|
160
|
+
Employee.where(first_name: row['First name'], last_name: row['Last name']).first_or_initialize
|
161
|
+
end
|
162
|
+
|
163
|
+
# ...
|
164
|
+
end
|
165
|
+
```
|
166
|
+
|
167
|
+
The code above specifies that, for each row, the importer should attempt to
|
168
|
+
find an existing model for the employee with the first and last name in the row
|
169
|
+
being processed. If this record exist, the row data will be used to update the
|
170
|
+
given model instance. Otherwise, a new employee record will be created.
|
171
|
+
|
135
172
|
## Contributing
|
136
173
|
|
137
174
|
1. Fork it
|
data/lib/active_importer/base.rb
CHANGED
@@ -26,6 +26,14 @@ module ActiveImporter
|
|
26
26
|
self.class.model_class
|
27
27
|
end
|
28
28
|
|
29
|
+
def self.fetch_model(&block)
|
30
|
+
@fetch_model_block = block
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.fetch_model_block
|
34
|
+
@fetch_model_block
|
35
|
+
end
|
36
|
+
|
29
37
|
def self.column(title, field = nil, &block)
|
30
38
|
title = title.strip
|
31
39
|
if columns[title]
|
@@ -45,6 +53,7 @@ module ActiveImporter
|
|
45
53
|
EVENTS = [
|
46
54
|
:row_success,
|
47
55
|
:row_error,
|
56
|
+
:row_processing,
|
48
57
|
:row_processed,
|
49
58
|
:import_started,
|
50
59
|
:import_finished,
|
@@ -77,6 +86,7 @@ module ActiveImporter
|
|
77
86
|
|
78
87
|
class << self
|
79
88
|
private :fire_event
|
89
|
+
private :fetch_model_block
|
80
90
|
end
|
81
91
|
|
82
92
|
#
|
@@ -104,8 +114,16 @@ module ActiveImporter
|
|
104
114
|
fire_event :import_failed, e
|
105
115
|
end
|
106
116
|
|
117
|
+
def fetch_model_block
|
118
|
+
self.class.send(:fetch_model_block)
|
119
|
+
end
|
120
|
+
|
107
121
|
def fetch_model
|
108
|
-
|
122
|
+
if fetch_model_block
|
123
|
+
self.instance_exec(&fetch_model_block)
|
124
|
+
else
|
125
|
+
model_class.new
|
126
|
+
end
|
109
127
|
end
|
110
128
|
|
111
129
|
def import
|
@@ -131,9 +149,6 @@ module ActiveImporter
|
|
131
149
|
row_errors.count
|
132
150
|
end
|
133
151
|
|
134
|
-
def hook
|
135
|
-
end
|
136
|
-
|
137
152
|
private
|
138
153
|
|
139
154
|
def columns
|
@@ -142,7 +157,7 @@ module ActiveImporter
|
|
142
157
|
|
143
158
|
def find_header_index
|
144
159
|
(1..@book.last_row).each do |index|
|
145
|
-
row = @book.row(index).map
|
160
|
+
row = @book.row(index).map { |cell| cell.to_s.strip }
|
146
161
|
return index if columns.keys.all? { |item| row.include?(item) }
|
147
162
|
end
|
148
163
|
return nil
|
@@ -182,7 +197,7 @@ module ActiveImporter
|
|
182
197
|
value = self.instance_exec(value, &transform) if transform
|
183
198
|
model[field_name] = value
|
184
199
|
end
|
185
|
-
|
200
|
+
fire_event :row_processing
|
186
201
|
end
|
187
202
|
|
188
203
|
def row_to_hash(row)
|
@@ -14,6 +14,7 @@ describe ActiveImporter::Base do
|
|
14
14
|
|
15
15
|
before do
|
16
16
|
expect(Roo::Spreadsheet).to receive(:open).and_return { Spreadsheet.new(spreadsheet_data) }
|
17
|
+
EmployeeImporter.instance_variable_set(:@fetch_model_block, nil)
|
17
18
|
end
|
18
19
|
|
19
20
|
it 'imports all data from the spreadsheet into the model' do
|
@@ -88,7 +89,7 @@ describe ActiveImporter::Base do
|
|
88
89
|
let(:spreadsheet_data) do
|
89
90
|
[
|
90
91
|
[],
|
91
|
-
['List of employees', '', 'Company Name'],
|
92
|
+
['List of employees', '', nil, 'Company Name'],
|
92
93
|
['Ordered by', 'Birth Date'],
|
93
94
|
['Name', 'Department', 'Birth Date', 'Manager'],
|
94
95
|
['John Doe', 'IT', '2013-10-25'],
|
@@ -102,19 +103,17 @@ describe ActiveImporter::Base do
|
|
102
103
|
end
|
103
104
|
|
104
105
|
describe '.fetch_model' do
|
105
|
-
let(:model) { Employee.new }
|
106
|
-
|
107
106
|
it 'controls what model instance is loaded for each given row' do
|
108
|
-
|
109
|
-
|
107
|
+
model = Employee.new
|
108
|
+
EmployeeImporter.fetch_model { model }
|
110
109
|
expect { EmployeeImporter.import('/dummy/file') }.to change(Employee, :count).by(1)
|
111
110
|
end
|
112
111
|
end
|
113
112
|
|
114
|
-
describe '
|
113
|
+
describe 'row_processing event' do
|
115
114
|
it 'allows the importer to modify the model for each row' do
|
116
115
|
expect(EmployeeImporter).to receive(:new).once.and_return(importer)
|
117
|
-
expect(importer).to receive(:
|
116
|
+
expect(importer).to receive(:row_processing).twice
|
118
117
|
EmployeeImporter.import('/dummy/file')
|
119
118
|
end
|
120
119
|
end
|
data/spec/stubs/data_model.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
class DataModel
|
2
|
-
@@count = 0
|
3
2
|
|
4
3
|
def self.count
|
5
|
-
|
4
|
+
@count ||= 0
|
6
5
|
end
|
7
6
|
|
8
7
|
attr_reader :errors
|
@@ -25,7 +24,7 @@ class DataModel
|
|
25
24
|
|
26
25
|
def save
|
27
26
|
if valid?
|
28
|
-
|
27
|
+
self.class.send(:increment_count) if @new_record
|
29
28
|
@new_record = false
|
30
29
|
true
|
31
30
|
else
|
@@ -43,10 +42,21 @@ class DataModel
|
|
43
42
|
|
44
43
|
def valid?
|
45
44
|
validate
|
46
|
-
|
45
|
+
errors.empty?
|
47
46
|
end
|
48
47
|
|
49
48
|
def validate
|
50
49
|
# ...
|
51
50
|
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def self.increment_count
|
55
|
+
count
|
56
|
+
@count += 1
|
57
|
+
end
|
58
|
+
|
59
|
+
class << self
|
60
|
+
private :increment_count
|
61
|
+
end
|
52
62
|
end
|
data/spec/stubs/employee.rb
CHANGED
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.
|
4
|
+
version: 0.2.0
|
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-11 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: -1076262899309802271
|
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: -1076262899309802271
|
125
125
|
requirements: []
|
126
126
|
rubyforge_project:
|
127
127
|
rubygems_version: 1.8.23
|