abstract_importer 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,2 +1,3 @@
1
1
  require "abstract_importer/strategies/default_strategy"
2
2
  require "abstract_importer/strategies/replace_strategy"
3
+ require "abstract_importer/strategies/insert_strategy"
@@ -11,6 +11,7 @@ module AbstractImporter
11
11
  :id_map,
12
12
  :scope,
13
13
  :reporter,
14
+ :association_attrs,
14
15
  to: :collection
15
16
 
16
17
  def initialize(collection)
@@ -25,6 +26,17 @@ module AbstractImporter
25
26
  id_map.contains? collection.table_name, hash[:id]
26
27
  end
27
28
 
29
+ def flush
30
+ end
31
+
32
+ def prepare_attributes(hash)
33
+ hash = invoke_callback(:before_build, hash) || hash
34
+
35
+ legacy_id = hash.delete(:id)
36
+
37
+ hash.merge(legacy_id: legacy_id).merge(association_attrs)
38
+ end
39
+
28
40
  end
29
41
  end
30
42
  end
@@ -58,13 +58,7 @@ module AbstractImporter
58
58
  end
59
59
 
60
60
  def build_record(hash)
61
- hash = invoke_callback(:before_build, hash) || hash
62
-
63
- legacy_id = hash.delete(:id)
64
-
65
- collection.model.new(hash
66
- .merge(legacy_id: legacy_id)
67
- .merge(collection.association_attrs))
61
+ collection.model.new prepare_attributes(hash)
68
62
  end
69
63
 
70
64
  def clean_record(record)
@@ -0,0 +1,58 @@
1
+ require "abstract_importer/strategies/base"
2
+ require "activerecord/insert_many"
3
+
4
+ module AbstractImporter
5
+ module Strategies
6
+ class InsertStrategy < Base
7
+
8
+ def initialize(collection)
9
+ super
10
+ @batch = []
11
+ @batch_size = 250
12
+ end
13
+
14
+
15
+ def process_record(hash)
16
+ summary.total += 1
17
+
18
+ if already_imported?(hash)
19
+ summary.already_imported += 1
20
+ return
21
+ end
22
+
23
+ remap_foreign_keys!(hash)
24
+
25
+ if redundant_record?(hash)
26
+ summary.redundant += 1
27
+ return
28
+ end
29
+
30
+ @batch << prepare_attributes(hash)
31
+ flush if @batch.length >= @batch_size
32
+
33
+ rescue ::AbstractImporter::Skip
34
+ summary.skipped += 1
35
+ end
36
+
37
+
38
+ def flush
39
+ invoke_callback(:before_batch, @batch)
40
+
41
+ begin
42
+ tries = (tries || 0) + 1
43
+ collection.scope.insert_many(@batch)
44
+ rescue
45
+ raise if tries > 1
46
+ invoke_callback(:rescue_batch, @batch)
47
+ retry
48
+ end
49
+
50
+ id_map.merge! collection.table_name, collection.scope
51
+ .where(legacy_id: @batch.map { |hash| hash[:legacy_id] })
52
+ @batch = []
53
+ end
54
+
55
+
56
+ end
57
+ end
58
+ end
@@ -1,14 +1,14 @@
1
1
  module AbstractImporter
2
2
  class Summary < Struct.new(:total, :redundant, :created, :already_imported, :invalid, :ms, :skipped)
3
-
3
+
4
4
  def initialize
5
5
  super(0,0,0,0,0,0,0)
6
6
  end
7
-
7
+
8
8
  def average_ms
9
9
  return nil if total == 0
10
10
  ms / total
11
11
  end
12
-
12
+
13
13
  end
14
14
  end
@@ -1,3 +1,3 @@
1
1
  module AbstractImporter
2
- VERSION = "1.2.1"
2
+ VERSION = "1.3.0"
3
3
  end
@@ -2,9 +2,9 @@ require "test_helper"
2
2
 
3
3
 
4
4
  class CallbackTest < ActiveSupport::TestCase
5
-
6
-
7
-
5
+
6
+
7
+
8
8
  context "before_build" do
9
9
  setup do
10
10
  plan do |import|
@@ -13,12 +13,12 @@ class CallbackTest < ActiveSupport::TestCase
13
13
  end
14
14
  end
15
15
  end
16
-
16
+
17
17
  should "should be invoked on the incoming attributes" do
18
18
  import!
19
19
  assert_equal ["Harry", "Ron", "Hermione"], account.students.pluck(:name)
20
20
  end
21
-
21
+
22
22
  should "allow you to skip certain records" do
23
23
  plan do |import|
24
24
  import.students do |options|
@@ -27,15 +27,15 @@ class CallbackTest < ActiveSupport::TestCase
27
27
  end
28
28
  end
29
29
  end
30
-
30
+
31
31
  import!
32
32
  assert_equal ["Ron Weasley", "Hermione Granger"], account.students.pluck(:name)
33
33
  assert_equal 1, results[:students].skipped
34
34
  end
35
35
  end
36
-
37
-
38
-
36
+
37
+
38
+
39
39
  context "before_create" do
40
40
  setup do
41
41
  plan do |import|
@@ -44,15 +44,15 @@ class CallbackTest < ActiveSupport::TestCase
44
44
  end
45
45
  end
46
46
  end
47
-
47
+
48
48
  should "should be invoked on imported records before they are saved" do
49
49
  import!
50
50
  assert_equal ["Gryffindor"], account.students.pluck(:house).uniq
51
51
  end
52
52
  end
53
-
54
-
55
-
53
+
54
+
55
+
56
56
  context "after_create" do
57
57
  setup do
58
58
  plan do |import|
@@ -61,7 +61,7 @@ class CallbackTest < ActiveSupport::TestCase
61
61
  end
62
62
  end
63
63
  end
64
-
64
+
65
65
  should "should be invoked after the record is created" do
66
66
  mock(importer).callback({name: "Harry Potter"}, satisfy(&:persisted?)).once
67
67
  mock(importer).callback({name: "Ron Weasley"}, satisfy(&:persisted?)).once
@@ -69,9 +69,9 @@ class CallbackTest < ActiveSupport::TestCase
69
69
  import!
70
70
  end
71
71
  end
72
-
73
-
74
-
72
+
73
+
74
+
75
75
  context "rescue" do
76
76
  setup do
77
77
  plan do |import|
@@ -80,15 +80,15 @@ class CallbackTest < ActiveSupport::TestCase
80
80
  end
81
81
  end
82
82
  end
83
-
83
+
84
84
  should "should be given a chance to amend an invalid record" do
85
85
  import!
86
86
  assert_equal ["godrics-hollow", "azkaban"], account.locations.pluck(:slug)
87
87
  end
88
88
  end
89
-
90
-
91
-
89
+
90
+
91
+
92
92
  context "before_all" do
93
93
  setup do
94
94
  plan do |import|
@@ -97,15 +97,15 @@ class CallbackTest < ActiveSupport::TestCase
97
97
  end
98
98
  end
99
99
  end
100
-
100
+
101
101
  should "should be invoked before the collection has been imported" do
102
102
  mock(importer).callback.once
103
103
  import!
104
104
  end
105
105
  end
106
-
107
-
108
-
106
+
107
+
108
+
109
109
  context "after_all" do
110
110
  setup do
111
111
  plan do |import|
@@ -114,13 +114,13 @@ class CallbackTest < ActiveSupport::TestCase
114
114
  end
115
115
  end
116
116
  end
117
-
117
+
118
118
  should "should be invoked after the collection has been imported" do
119
119
  mock(importer).callback.once
120
120
  import!
121
121
  end
122
122
  end
123
-
124
-
125
-
123
+
124
+
125
+
126
126
  end
@@ -1,30 +1,30 @@
1
1
  require "test_helper"
2
2
 
3
3
 
4
- class ImporterTest < ActiveSupport::TestCase
5
-
6
-
7
-
4
+ class DefaultStrategyTest < ActiveSupport::TestCase
5
+
6
+
7
+
8
8
  context "with a simple data source" do
9
9
  setup do
10
10
  plan do |import|
11
11
  import.students
12
12
  end
13
13
  end
14
-
14
+
15
15
  should "import the given records" do
16
16
  import!
17
17
  assert_equal ["Harry Potter", "Ron Weasley", "Hermione Granger"], account.students.pluck(:name)
18
18
  end
19
-
19
+
20
20
  should "record their legacy_id" do
21
21
  import!
22
22
  assert_equal [456, 457, 458], account.students.pluck(:legacy_id)
23
23
  end
24
24
  end
25
-
26
-
27
-
25
+
26
+
27
+
28
28
  context "with a complex data source" do
29
29
  setup do
30
30
  plan do |import|
@@ -32,19 +32,19 @@ class ImporterTest < ActiveSupport::TestCase
32
32
  import.parents
33
33
  end
34
34
  end
35
-
35
+
36
36
  should "preserve mappings" do
37
37
  import!
38
38
  harry = account.students.find_by_name("Harry Potter")
39
39
  assert_equal ["James Potter", "Lily Potter"], harry.parents.pluck(:name)
40
40
  end
41
-
41
+
42
42
  should "preserve mappings even when a record was previously imported" do
43
43
  harry = account.students.create!(name: "Harry Potter", legacy_id: 456)
44
44
  import!
45
45
  assert_equal ["James Potter", "Lily Potter"], harry.parents.pluck(:name)
46
46
  end
47
-
47
+
48
48
  context "when {atomic: true}" do
49
49
  should "rollback the whole import if an part fails" do
50
50
  mock(importer).atomic? { true }
@@ -54,7 +54,7 @@ class ImporterTest < ActiveSupport::TestCase
54
54
  assert_equal 0, account.students.count, "Expected students to have been rolled back"
55
55
  end
56
56
  end
57
-
57
+
58
58
  context "when {atomic: false}" do
59
59
  should "not rollback the whole import if an part fails" do
60
60
  mock(importer).atomic? { false }
@@ -65,9 +65,9 @@ class ImporterTest < ActiveSupport::TestCase
65
65
  end
66
66
  end
67
67
  end
68
-
69
-
70
-
68
+
69
+
70
+
71
71
  context "with a dependency" do
72
72
  setup do
73
73
  depends_on :students
@@ -75,16 +75,16 @@ class ImporterTest < ActiveSupport::TestCase
75
75
  import.parents
76
76
  end
77
77
  end
78
-
78
+
79
79
  should "preserve mappings when a dependency was imported by another importer" do
80
80
  harry = account.students.create!(name: "Harry Potter", legacy_id: 456)
81
81
  import!
82
82
  assert_equal ["James Potter", "Lily Potter"], harry.parents.pluck(:name)
83
83
  end
84
84
  end
85
-
86
-
87
-
85
+
86
+
87
+
88
88
  context "when a finder is specified" do
89
89
  setup do
90
90
  plan do |import|
@@ -94,22 +94,22 @@ class ImporterTest < ActiveSupport::TestCase
94
94
  import.parents
95
95
  end
96
96
  end
97
-
97
+
98
98
  should "not import redundant records" do
99
99
  account.students.create!(name: "Ron Weasley", legacy_id: nil)
100
100
  import!
101
101
  assert_equal 3, account.students.count
102
102
  end
103
-
103
+
104
104
  should "preserve mappings" do
105
105
  harry = account.students.create!(name: "Harry Potter", legacy_id: nil)
106
106
  import!
107
107
  assert_equal ["James Potter", "Lily Potter"], harry.parents.pluck(:name)
108
108
  end
109
109
  end
110
-
111
-
112
-
110
+
111
+
112
+
113
113
  context "with a more complex data source" do
114
114
  setup do
115
115
  plan do |import|
@@ -124,16 +124,16 @@ class ImporterTest < ActiveSupport::TestCase
124
124
  import.grades
125
125
  end
126
126
  end
127
-
127
+
128
128
  should "preserve mappings" do
129
129
  import!
130
130
  ron = account.students.find_by_name "Ron Weasley"
131
131
  assert_equal ["Advanced Potions: Acceptable", "History of Magic: Troll"], ron.report_card
132
132
  end
133
133
  end
134
-
135
-
136
-
134
+
135
+
136
+
137
137
  context "with polymorphic associations" do
138
138
  setup do
139
139
  plan do |import|
@@ -142,7 +142,7 @@ class ImporterTest < ActiveSupport::TestCase
142
142
  import.students
143
143
  end
144
144
  end
145
-
145
+
146
146
  should "preserve mappings" do
147
147
  import!
148
148
  assert_equal 2, account.students.map(&:pet).compact.count, "Expected two students to still be linked to their pets upon import"
@@ -150,30 +150,25 @@ class ImporterTest < ActiveSupport::TestCase
150
150
  assert_kind_of Cat, account.students.find_by_name("Hermione Granger").pet, "Expected Harry's pet to be a Cat"
151
151
  end
152
152
  end
153
-
154
-
155
-
156
- context "When we use the default strategy" do
153
+
154
+
155
+
156
+ context "When records already exist" do
157
157
  setup do
158
158
  plan do |import|
159
159
  import.students
160
160
  end
161
+ account.students.create!(name: "Ron Weasley", legacy_id: 457)
161
162
  end
162
-
163
- context "and records already exist" do
164
- setup do
165
- account.students.create!(name: "Ron Weasley", legacy_id: 457)
166
- end
167
-
168
- should "not import existing records twice" do
169
- import!
170
- assert_equal 3, account.students.count
171
- end
163
+
164
+ should "not import existing records twice" do
165
+ import!
166
+ assert_equal 3, account.students.count
172
167
  end
173
168
  end
174
-
175
-
176
-
169
+
170
+
171
+
177
172
  context "When we specify collections to skip" do
178
173
  setup do
179
174
  plan do |import|
@@ -181,24 +176,24 @@ class ImporterTest < ActiveSupport::TestCase
181
176
  import.parents
182
177
  end
183
178
  end
184
-
179
+
185
180
  context "using :skip" do
186
181
  setup do
187
182
  options.merge!(skip: :parents)
188
183
  end
189
-
184
+
190
185
  should "not import the named collections" do
191
186
  import!
192
187
  assert_equal 3, account.students.length
193
188
  assert_equal 0, account.parents.length
194
189
  end
195
190
  end
196
-
191
+
197
192
  context "using :only" do
198
193
  setup do
199
194
  options.merge!(only: [:students])
200
195
  end
201
-
196
+
202
197
  should "import only the named collections" do
203
198
  import!
204
199
  assert_equal 3, account.students.length
@@ -206,30 +201,7 @@ class ImporterTest < ActiveSupport::TestCase
206
201
  end
207
202
  end
208
203
  end
209
-
210
-
211
-
212
- context "When we use the :replace strategy" do
213
- setup do
214
- options.merge!(strategy: {students: :replace})
215
- plan do |import|
216
- import.students
217
- end
218
- end
219
-
220
- context "and records already exist" do
221
- setup do
222
- account.students.create!(name: "Ron Weasley", legacy_id: 457)
223
- end
224
-
225
- should "reimport the existing records" do
226
- import!
227
- assert_equal "Gryffindor", account.students.find_by_name("Ron Weasley").house,
228
- "Expected Ron's record to have been replaced with one that has a house"
229
- end
230
- end
231
- end
232
-
233
-
234
-
204
+
205
+
206
+
235
207
  end