abstract_importer 1.2.1 → 1.3.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.
@@ -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