activerecord-import 0.14.1 → 1.4.1

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.
Files changed (131) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yaml +107 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +74 -8
  5. data/Brewfile +3 -1
  6. data/CHANGELOG.md +448 -2
  7. data/Gemfile +26 -19
  8. data/LICENSE +21 -56
  9. data/README.markdown +568 -32
  10. data/Rakefile +5 -1
  11. data/activerecord-import.gemspec +8 -7
  12. data/benchmarks/README +2 -2
  13. data/benchmarks/benchmark.rb +8 -1
  14. data/benchmarks/lib/base.rb +2 -0
  15. data/benchmarks/lib/cli_parser.rb +5 -2
  16. data/benchmarks/lib/float.rb +2 -0
  17. data/benchmarks/lib/mysql2_benchmark.rb +2 -0
  18. data/benchmarks/lib/output_to_csv.rb +2 -0
  19. data/benchmarks/lib/output_to_html.rb +4 -2
  20. data/benchmarks/models/test_innodb.rb +2 -0
  21. data/benchmarks/models/test_memory.rb +2 -0
  22. data/benchmarks/models/test_myisam.rb +2 -0
  23. data/benchmarks/schema/{mysql_schema.rb → mysql2_schema.rb} +2 -0
  24. data/gemfiles/4.2.gemfile +4 -3
  25. data/gemfiles/5.0.gemfile +4 -3
  26. data/gemfiles/5.1.gemfile +4 -0
  27. data/gemfiles/5.2.gemfile +4 -0
  28. data/gemfiles/6.0.gemfile +4 -0
  29. data/gemfiles/6.1.gemfile +4 -0
  30. data/gemfiles/7.0.gemfile +4 -0
  31. data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +2 -0
  32. data/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +6 -4
  33. data/lib/activerecord-import/active_record/adapters/jdbcpostgresql_adapter.rb +2 -0
  34. data/lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb +8 -0
  35. data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +2 -0
  36. data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +2 -0
  37. data/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb +2 -0
  38. data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +2 -0
  39. data/lib/activerecord-import/adapters/abstract_adapter.rb +12 -16
  40. data/lib/activerecord-import/adapters/em_mysql2_adapter.rb +2 -0
  41. data/lib/activerecord-import/adapters/mysql2_adapter.rb +2 -0
  42. data/lib/activerecord-import/adapters/mysql_adapter.rb +35 -18
  43. data/lib/activerecord-import/adapters/postgresql_adapter.rb +107 -28
  44. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +141 -18
  45. data/lib/activerecord-import/base.rb +15 -8
  46. data/lib/activerecord-import/import.rb +642 -178
  47. data/lib/activerecord-import/mysql2.rb +2 -0
  48. data/lib/activerecord-import/postgresql.rb +2 -0
  49. data/lib/activerecord-import/sqlite3.rb +2 -0
  50. data/lib/activerecord-import/synchronize.rb +5 -3
  51. data/lib/activerecord-import/value_sets_parser.rb +29 -3
  52. data/lib/activerecord-import/version.rb +3 -1
  53. data/lib/activerecord-import.rb +5 -16
  54. data/test/adapters/jdbcmysql.rb +2 -0
  55. data/test/adapters/jdbcpostgresql.rb +2 -0
  56. data/test/adapters/jdbcsqlite3.rb +3 -0
  57. data/test/adapters/makara_postgis.rb +3 -0
  58. data/test/adapters/mysql2.rb +2 -0
  59. data/test/adapters/mysql2_makara.rb +2 -0
  60. data/test/adapters/mysql2spatial.rb +2 -0
  61. data/test/adapters/postgis.rb +2 -0
  62. data/test/adapters/postgresql.rb +2 -0
  63. data/test/adapters/postgresql_makara.rb +2 -0
  64. data/test/adapters/seamless_database_pool.rb +2 -0
  65. data/test/adapters/spatialite.rb +2 -0
  66. data/test/adapters/sqlite3.rb +2 -0
  67. data/test/{travis → github}/database.yml +7 -1
  68. data/test/import_test.rb +498 -32
  69. data/test/jdbcmysql/import_test.rb +2 -1
  70. data/test/jdbcpostgresql/import_test.rb +2 -1
  71. data/test/jdbcsqlite3/import_test.rb +6 -0
  72. data/test/makara_postgis/import_test.rb +10 -0
  73. data/test/models/account.rb +5 -0
  74. data/test/models/alarm.rb +4 -0
  75. data/test/models/animal.rb +8 -0
  76. data/test/models/bike_maker.rb +9 -0
  77. data/test/models/book.rb +4 -0
  78. data/test/models/car.rb +5 -0
  79. data/test/models/card.rb +5 -0
  80. data/test/models/chapter.rb +2 -0
  81. data/test/models/customer.rb +8 -0
  82. data/test/models/deck.rb +8 -0
  83. data/test/models/dictionary.rb +6 -0
  84. data/test/models/discount.rb +2 -0
  85. data/test/models/end_note.rb +2 -0
  86. data/test/models/group.rb +2 -0
  87. data/test/models/order.rb +8 -0
  88. data/test/models/playing_card.rb +4 -0
  89. data/test/models/promotion.rb +2 -0
  90. data/test/models/question.rb +2 -0
  91. data/test/models/rule.rb +2 -0
  92. data/test/models/tag.rb +7 -0
  93. data/test/models/tag_alias.rb +5 -0
  94. data/test/models/topic.rb +16 -0
  95. data/test/models/user.rb +5 -0
  96. data/test/models/user_token.rb +6 -0
  97. data/test/models/vendor.rb +9 -0
  98. data/test/models/widget.rb +18 -0
  99. data/test/mysql2/import_test.rb +2 -0
  100. data/test/mysql2_makara/import_test.rb +2 -0
  101. data/test/mysqlspatial2/import_test.rb +2 -0
  102. data/test/postgis/import_test.rb +6 -0
  103. data/test/postgresql/import_test.rb +2 -4
  104. data/test/schema/generic_schema.rb +88 -3
  105. data/test/schema/jdbcpostgresql_schema.rb +3 -0
  106. data/test/schema/mysql2_schema.rb +21 -0
  107. data/test/schema/postgis_schema.rb +3 -0
  108. data/test/schema/postgresql_schema.rb +63 -0
  109. data/test/schema/sqlite3_schema.rb +15 -0
  110. data/test/schema/version.rb +2 -0
  111. data/test/sqlite3/import_test.rb +4 -50
  112. data/test/support/active_support/test_case_extensions.rb +8 -1
  113. data/test/support/assertions.rb +2 -0
  114. data/test/support/factories.rb +17 -8
  115. data/test/support/generate.rb +10 -8
  116. data/test/support/mysql/import_examples.rb +17 -3
  117. data/test/support/postgresql/import_examples.rb +442 -9
  118. data/test/support/shared_examples/on_duplicate_key_ignore.rb +45 -0
  119. data/test/support/shared_examples/on_duplicate_key_update.rb +278 -1
  120. data/test/support/shared_examples/recursive_import.rb +137 -12
  121. data/test/support/sqlite3/import_examples.rb +232 -0
  122. data/test/synchronize_test.rb +10 -0
  123. data/test/test_helper.rb +44 -3
  124. data/test/value_sets_bytes_parser_test.rb +15 -2
  125. data/test/value_sets_records_parser_test.rb +2 -0
  126. metadata +74 -22
  127. data/.travis.yml +0 -52
  128. data/gemfiles/3.2.gemfile +0 -3
  129. data/gemfiles/4.0.gemfile +0 -3
  130. data/gemfiles/4.1.gemfile +0 -3
  131. data/test/schema/mysql_schema.rb +0 -16
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ def should_support_on_duplicate_key_ignore
4
+ describe "#import" do
5
+ extend ActiveSupport::TestCase::ImportAssertions
6
+ let(:topic) { Topic.create!(title: "Book", author_name: "John Doe") }
7
+ let(:topics) { [topic] }
8
+
9
+ context "with :on_duplicate_key_ignore" do
10
+ it "should skip duplicates and continue import" do
11
+ topics << Topic.new(title: "Book 2", author_name: "Jane Doe")
12
+ assert_difference "Topic.count", +1 do
13
+ result = Topic.import topics, on_duplicate_key_ignore: true, validate: false
14
+ assert_not_equal topics.first.id, result.ids.first
15
+ assert_nil topics.last.id
16
+ end
17
+ end
18
+
19
+ unless ENV["SKIP_COMPOSITE_PK"]
20
+ context "with composite primary keys" do
21
+ it "should import array of values successfully" do
22
+ columns = [:tag_id, :publisher_id, :tag]
23
+ values = [[1, 1, 'Mystery'], [1, 1, 'Science']]
24
+
25
+ assert_difference "Tag.count", +1 do
26
+ Tag.import columns, values, on_duplicate_key_ignore: true, validate: false
27
+ end
28
+ assert_equal 'Mystery', Tag.first.tag
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ context "with :ignore" do
35
+ it "should skip duplicates and continue import" do
36
+ topics << Topic.new(title: "Book 2", author_name: "Jane Doe")
37
+ assert_difference "Topic.count", +1 do
38
+ result = Topic.import topics, ignore: true, validate: false
39
+ assert_not_equal topics.first.id, result.ids.first
40
+ assert_nil topics.last.id
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  def should_support_basic_on_duplicate_key_update
2
4
  describe "#import" do
3
5
  extend ActiveSupport::TestCase::ImportAssertions
@@ -5,9 +7,250 @@ def should_support_basic_on_duplicate_key_update
5
7
  macro(:perform_import) { raise "supply your own #perform_import in a context below" }
6
8
  macro(:updated_topic) { Topic.find(@topic.id) }
7
9
 
10
+ context "with lock_version upsert" do
11
+ describe 'optimistic lock' do
12
+ it 'lock_version upsert after on_duplcate_key_update by model' do
13
+ users = [
14
+ User.new(name: 'Salomon'),
15
+ User.new(name: 'Nathan')
16
+ ]
17
+ User.import(users)
18
+ assert User.count == users.length
19
+ User.all.each do |user|
20
+ assert_equal 0, user.lock_version
21
+ end
22
+ updated_users = User.all.map do |user|
23
+ user.name += ' Rothschild'
24
+ user
25
+ end
26
+ User.import(updated_users, on_duplicate_key_update: [:name])
27
+ assert User.count == updated_users.length
28
+ User.all.each_with_index do |user, i|
29
+ assert_equal user.name, users[i].name + ' Rothschild'
30
+ assert_equal 1, user.lock_version
31
+ end
32
+ end
33
+
34
+ it 'lock_version upsert after on_duplcate_key_update by array' do
35
+ users = [
36
+ User.new(name: 'Salomon'),
37
+ User.new(name: 'Nathan')
38
+ ]
39
+ User.import(users)
40
+ assert User.count == users.length
41
+ User.all.each do |user|
42
+ assert_equal 0, user.lock_version
43
+ end
44
+
45
+ columns = [:id, :name]
46
+ updated_values = User.all.map do |user|
47
+ user.name += ' Rothschild'
48
+ [user.id, user.name]
49
+ end
50
+ User.import(columns, updated_values, on_duplicate_key_update: [:name])
51
+ assert User.count == updated_values.length
52
+ User.all.each_with_index do |user, i|
53
+ assert_equal user.name, users[i].name + ' Rothschild'
54
+ assert_equal 1, user.lock_version
55
+ end
56
+ end
57
+
58
+ it 'lock_version upsert after on_duplcate_key_update by hash' do
59
+ users = [
60
+ User.new(name: 'Salomon'),
61
+ User.new(name: 'Nathan')
62
+ ]
63
+ User.import(users)
64
+ assert User.count == users.length
65
+ User.all.each do |user|
66
+ assert_equal 0, user.lock_version
67
+ end
68
+ updated_values = User.all.map do |user|
69
+ user.name += ' Rothschild'
70
+ { id: user.id, name: user.name }
71
+ end
72
+ User.import(updated_values, on_duplicate_key_update: [:name])
73
+ assert User.count == updated_values.length
74
+ User.all.each_with_index do |user, i|
75
+ assert_equal user.name, users[i].name + ' Rothschild'
76
+ assert_equal 1, user.lock_version
77
+ end
78
+ updated_values2 = User.all.map do |user|
79
+ user.name += ' jr.'
80
+ { id: user.id, name: user.name }
81
+ end
82
+ User.import(updated_values2, on_duplicate_key_update: [:name])
83
+ assert User.count == updated_values2.length
84
+ User.all.each_with_index do |user, i|
85
+ assert_equal user.name, users[i].name + ' Rothschild jr.'
86
+ assert_equal 2, user.lock_version
87
+ end
88
+ end
89
+
90
+ it 'upsert optimistic lock columns other than lock_version by model' do
91
+ accounts = [
92
+ Account.new(name: 'Salomon'),
93
+ Account.new(name: 'Nathan')
94
+ ]
95
+ Account.import(accounts)
96
+ assert Account.count == accounts.length
97
+ Account.all.each do |user|
98
+ assert_equal 0, user.lock
99
+ end
100
+ updated_accounts = Account.all.map do |user|
101
+ user.name += ' Rothschild'
102
+ user
103
+ end
104
+ Account.import(updated_accounts, on_duplicate_key_update: [:id, :name])
105
+ assert Account.count == updated_accounts.length
106
+ Account.all.each_with_index do |user, i|
107
+ assert_equal user.name, accounts[i].name + ' Rothschild'
108
+ assert_equal 1, user.lock
109
+ end
110
+ end
111
+
112
+ it 'upsert optimistic lock columns other than lock_version by array' do
113
+ accounts = [
114
+ Account.new(name: 'Salomon'),
115
+ Account.new(name: 'Nathan')
116
+ ]
117
+ Account.import(accounts)
118
+ assert Account.count == accounts.length
119
+ Account.all.each do |user|
120
+ assert_equal 0, user.lock
121
+ end
122
+
123
+ columns = [:id, :name]
124
+ updated_values = Account.all.map do |user|
125
+ user.name += ' Rothschild'
126
+ [user.id, user.name]
127
+ end
128
+ Account.import(columns, updated_values, on_duplicate_key_update: [:name])
129
+ assert Account.count == updated_values.length
130
+ Account.all.each_with_index do |user, i|
131
+ assert_equal user.name, accounts[i].name + ' Rothschild'
132
+ assert_equal 1, user.lock
133
+ end
134
+ end
135
+
136
+ it 'upsert optimistic lock columns other than lock_version by hash' do
137
+ accounts = [
138
+ Account.new(name: 'Salomon'),
139
+ Account.new(name: 'Nathan')
140
+ ]
141
+ Account.import(accounts)
142
+ assert Account.count == accounts.length
143
+ Account.all.each do |user|
144
+ assert_equal 0, user.lock
145
+ end
146
+ updated_values = Account.all.map do |user|
147
+ user.name += ' Rothschild'
148
+ { id: user.id, name: user.name }
149
+ end
150
+ Account.import(updated_values, on_duplicate_key_update: [:name])
151
+ assert Account.count == updated_values.length
152
+ Account.all.each_with_index do |user, i|
153
+ assert_equal user.name, accounts[i].name + ' Rothschild'
154
+ assert_equal 1, user.lock
155
+ end
156
+ end
157
+
158
+ it 'update the lock_version of models separated by namespaces by model' do
159
+ makers = [
160
+ Bike::Maker.new(name: 'Yamaha'),
161
+ Bike::Maker.new(name: 'Honda')
162
+ ]
163
+ Bike::Maker.import(makers)
164
+ assert Bike::Maker.count == makers.length
165
+ Bike::Maker.all.each do |maker|
166
+ assert_equal 0, maker.lock_version
167
+ end
168
+ updated_makers = Bike::Maker.all.map do |maker|
169
+ maker.name += ' bikes'
170
+ maker
171
+ end
172
+ Bike::Maker.import(updated_makers, on_duplicate_key_update: [:name])
173
+ assert Bike::Maker.count == updated_makers.length
174
+ Bike::Maker.all.each_with_index do |maker, i|
175
+ assert_equal maker.name, makers[i].name + ' bikes'
176
+ assert_equal 1, maker.lock_version
177
+ end
178
+ end
179
+ it 'update the lock_version of models separated by namespaces by array' do
180
+ makers = [
181
+ Bike::Maker.new(name: 'Yamaha'),
182
+ Bike::Maker.new(name: 'Honda')
183
+ ]
184
+ Bike::Maker.import(makers)
185
+ assert Bike::Maker.count == makers.length
186
+ Bike::Maker.all.each do |maker|
187
+ assert_equal 0, maker.lock_version
188
+ end
189
+
190
+ columns = [:id, :name]
191
+ updated_values = Bike::Maker.all.map do |maker|
192
+ maker.name += ' bikes'
193
+ [maker.id, maker.name]
194
+ end
195
+ Bike::Maker.import(columns, updated_values, on_duplicate_key_update: [:name])
196
+ assert Bike::Maker.count == updated_values.length
197
+ Bike::Maker.all.each_with_index do |maker, i|
198
+ assert_equal maker.name, makers[i].name + ' bikes'
199
+ assert_equal 1, maker.lock_version
200
+ end
201
+ end
202
+
203
+ it 'update the lock_version of models separated by namespaces by hash' do
204
+ makers = [
205
+ Bike::Maker.new(name: 'Yamaha'),
206
+ Bike::Maker.new(name: 'Honda')
207
+ ]
208
+ Bike::Maker.import(makers)
209
+ assert Bike::Maker.count == makers.length
210
+ Bike::Maker.all.each do |maker|
211
+ assert_equal 0, maker.lock_version
212
+ end
213
+ updated_values = Bike::Maker.all.map do |maker|
214
+ maker.name += ' bikes'
215
+ { id: maker.id, name: maker.name }
216
+ end
217
+ Bike::Maker.import(updated_values, on_duplicate_key_update: [:name])
218
+ assert Bike::Maker.count == updated_values.length
219
+ Bike::Maker.all.each_with_index do |maker, i|
220
+ assert_equal maker.name, makers[i].name + ' bikes'
221
+ assert_equal 1, maker.lock_version
222
+ end
223
+ end
224
+ end
225
+ end
226
+
8
227
  context "with :on_duplicate_key_update" do
228
+ describe 'using :all' do
229
+ let(:columns) { %w( id title author_name author_email_address parent_id ) }
230
+ let(:updated_values) { [[99, "Book - 2nd Edition", "Jane Doe", "janedoe@example.com", 57]] }
231
+
232
+ macro(:perform_import) do |*opts|
233
+ Topic.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: :all, validate: false)
234
+ end
235
+
236
+ setup do
237
+ values = [[99, "Book", "John Doe", "john@doe.com", 17, 3]]
238
+ Topic.import columns + ['replies_count'], values, validate: false
239
+ end
240
+
241
+ it 'updates all specified columns' do
242
+ perform_import
243
+ updated_topic = Topic.find(99)
244
+ assert_equal 'Book - 2nd Edition', updated_topic.title
245
+ assert_equal 'Jane Doe', updated_topic.author_name
246
+ assert_equal 'janedoe@example.com', updated_topic.author_email_address
247
+ assert_equal 57, updated_topic.parent_id
248
+ assert_equal 3, updated_topic.replies_count
249
+ end
250
+ end
251
+
9
252
  describe "argument safety" do
10
- it "should not modify the passed in :on_duplicate_key_update columns array" do
253
+ it "should not modify the passed in :on_duplicate_key_update array" do
11
254
  assert_nothing_raised do
12
255
  columns = %w(title author_name).freeze
13
256
  Topic.import columns, [%w(foo, bar)], on_duplicate_key_update: columns
@@ -15,6 +258,26 @@ def should_support_basic_on_duplicate_key_update
15
258
  end
16
259
  end
17
260
 
261
+ context "with timestamps enabled" do
262
+ let(:time) { Chronic.parse("5 minutes from now") }
263
+
264
+ it 'should not overwrite changed updated_at with current timestamp' do
265
+ topic = Topic.create(author_name: "Jane Doe", title: "Book")
266
+ timestamp = Time.now.utc
267
+ topic.updated_at = timestamp
268
+ Topic.import [topic], on_duplicate_key_update: :all, validate: false
269
+ assert_equal timestamp.to_s, Topic.last.updated_at.to_s
270
+ end
271
+
272
+ it 'should update updated_at with current timestamp' do
273
+ topic = Topic.create(author_name: "Jane Doe", title: "Book")
274
+ Timecop.freeze(time) do
275
+ Topic.import [topic], on_duplicate_key_update: [:updated_at], validate: false
276
+ assert_in_delta time.to_i, topic.reload.updated_at.to_i, 1.second
277
+ end
278
+ end
279
+ end
280
+
18
281
  context "with validation checks turned off" do
19
282
  asssertion_group(:should_support_on_duplicate_key_update) do
20
283
  should_not_update_fields_not_mentioned
@@ -76,6 +339,20 @@ def should_support_basic_on_duplicate_key_update
76
339
  assert_equal 'DISCOUNT2', updated_promotion.code
77
340
  end
78
341
  end
342
+
343
+ unless ENV["SKIP_COMPOSITE_PK"]
344
+ context "with composite primary keys" do
345
+ it "should import array of values successfully" do
346
+ columns = [:tag_id, :publisher_id, :tag]
347
+ Tag.import columns, [[1, 1, 'Mystery']], validate: false
348
+
349
+ assert_difference "Tag.count", +0 do
350
+ Tag.import columns, [[1, 1, 'Science']], on_duplicate_key_update: [:tag], validate: false
351
+ end
352
+ assert_equal 'Science', Tag.first.tag
353
+ end
354
+ end
355
+ end
79
356
  end
80
357
 
81
358
  context "with :on_duplicate_key_update turned off" do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  def should_support_recursive_import
2
4
  describe "importing objects with associations" do
3
5
  let(:new_topics) { Build(num_topics, :topic_with_book) }
@@ -11,7 +13,7 @@ def should_support_recursive_import
11
13
  let(:num_chapters) { 18 }
12
14
  let(:num_endnotes) { 24 }
13
15
 
14
- let(:new_question_with_rule) { FactoryGirl.build :question, :with_rule }
16
+ let(:new_question_with_rule) { FactoryBot.build :question, :with_rule }
15
17
 
16
18
  it 'imports top level' do
17
19
  assert_difference "Topic.count", +num_topics do
@@ -48,6 +50,22 @@ def should_support_recursive_import
48
50
  end
49
51
  end
50
52
 
53
+ it 'imports polymorphic associations from subclass' do
54
+ discounts = Array.new(1) { |i| Discount.new(amount: i) }
55
+ dictionaries = Array.new(1) { |i| Dictionary.new(author_name: "Author ##{i}", title: "Book ##{i}") }
56
+ dictionaries.each do |dictionary|
57
+ dictionary.discounts << discounts
58
+ end
59
+ Dictionary.import dictionaries, recursive: true
60
+ assert_equal 1, Dictionary.last.discounts.count
61
+ dictionaries.each do |dictionary|
62
+ dictionary.discounts.each do |discount|
63
+ assert_not_nil discount.discountable_id
64
+ assert_equal 'Book', discount.discountable_type
65
+ end
66
+ end
67
+ end
68
+
51
69
  [{ recursive: false }, {}].each do |import_options|
52
70
  it "skips recursion for #{import_options}" do
53
71
  assert_difference "Book.count", 0 do
@@ -74,6 +92,19 @@ def should_support_recursive_import
74
92
  end
75
93
  end
76
94
 
95
+ # Models are only valid if all associations are valid
96
+ it "only imports models with valid associations" do
97
+ assert_difference "Topic.count", 2 do
98
+ assert_difference "Book.count", 4 do
99
+ assert_difference "Chapter.count", 12 do
100
+ assert_difference "EndNote.count", 16 do
101
+ Topic.import new_topics_with_invalid_chapter, recursive: true
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+
77
108
  it "skips validation of the associations if requested" do
78
109
  assert_difference "Chapter.count", +num_chapters do
79
110
  Topic.import new_topics_with_invalid_chapter, validate: false, recursive: true
@@ -86,32 +117,126 @@ def should_support_recursive_import
86
117
  end
87
118
  end
88
119
 
89
- # These models dont validate associated. So we expect that books and topics get inserted, but not chapters
90
- # Putting a transaction around everything wouldn't work, so if you want your chapters to prevent topics from
91
- # being created, you would need to have validates_associated in your models and insert with validation
120
+ it "imports an imported belongs_to association id" do
121
+ first_new_topic = new_topics[0]
122
+ second_new_topic = new_topics[1]
123
+
124
+ books = first_new_topic.books.to_a
125
+ Topic.import new_topics, validate: false
126
+
127
+ assert_difference "Book.count", books.size do
128
+ Book.import books, validate: false
129
+ end
130
+
131
+ books.each do |book|
132
+ assert_equal book.topic_id, first_new_topic.id
133
+ end
134
+
135
+ books.each { |book| book.topic_id = second_new_topic.id }
136
+ assert_no_difference "Book.count", books.size do
137
+ Book.import books, validate: false, on_duplicate_key_update: [:topic_id]
138
+ end
139
+
140
+ books.each do |book|
141
+ assert_equal book.topic_id, second_new_topic.id
142
+ end
143
+
144
+ books.each { |book| book.topic_id = nil }
145
+ assert_no_difference "Book.count", books.size do
146
+ Book.import books, validate: false, on_duplicate_key_update: [:topic_id]
147
+ end
148
+
149
+ books.each do |book|
150
+ assert_equal book.topic_id, nil
151
+ end
152
+ end
153
+
154
+ unless ENV["SKIP_COMPOSITE_PK"]
155
+ describe "with composite primary keys" do
156
+ it "should import models and set id" do
157
+ tags = []
158
+ tags << Tag.new(tag_id: 1, publisher_id: 1, tag: 'Mystery')
159
+ tags << Tag.new(tag_id: 2, publisher_id: 1, tag: 'Science')
160
+
161
+ assert_difference "Tag.count", +2 do
162
+ Tag.import tags
163
+ end
164
+
165
+ assert_equal 1, tags[0].tag_id
166
+ assert_equal 2, tags[1].tag_id
167
+ end
168
+ end
169
+ end
170
+
92
171
  describe "all_or_none" do
93
- [Book, Topic, EndNote].each do |type|
172
+ [Book, Chapter, Topic, EndNote].each do |type|
94
173
  it "creates #{type}" do
95
- assert_difference "#{type}.count", send("num_#{type.to_s.downcase}s") do
174
+ assert_difference "#{type}.count", 0 do
96
175
  Topic.import new_topics_with_invalid_chapter, all_or_none: true, recursive: true
97
176
  end
98
177
  end
99
178
  end
100
- it "doesn't create chapters" do
101
- assert_difference "Chapter.count", 0 do
102
- Topic.import new_topics_with_invalid_chapter, all_or_none: true, recursive: true
179
+ end
180
+
181
+ # If adapter supports on_duplicate_key_update and specific columns are specified, it is only applied to top level models so that SQL with invalid
182
+ # columns, keys, etc isn't generated for child associations when doing recursive import
183
+ if ActiveRecord::Base.connection.supports_on_duplicate_key_update?
184
+ describe "on_duplicate_key_update" do
185
+ let(:new_topics) { Build(1, :topic_with_book) }
186
+
187
+ it "imports objects with associations" do
188
+ assert_difference "Topic.count", +1 do
189
+ Topic.import new_topics, recursive: true, on_duplicate_key_update: [:updated_at], validate: false
190
+ new_topics.each do |topic|
191
+ assert_not_nil topic.id
192
+ end
193
+ end
194
+ end
195
+
196
+ context "when :all fields are updated" do
197
+ setup do
198
+ Topic.import new_topics, recursive: true
199
+ end
200
+
201
+ it "updates associated objects" do
202
+ new_author_name = 'Richard Bachman'
203
+ topic = new_topics.first
204
+ topic.books.each do |book|
205
+ book.author_name = new_author_name
206
+ end
207
+ assert_nothing_raised do
208
+ Topic.import new_topics, recursive: true, on_duplicate_key_update: :all
209
+ end
210
+ Topic.find(topic.id).books.each do |book|
211
+ assert_equal new_author_name, book.author_name
212
+ end
213
+ end
103
214
  end
104
215
  end
105
216
  end
106
217
 
107
- # If adapter supports on_duplicate_key_update, it is only applied to top level models so that SQL with invalid
218
+ # If returning option is provided, it is only applied to top level models so that SQL with invalid
108
219
  # columns, keys, etc isn't generated for child associations when doing recursive import
109
- describe "on_duplicate_key_update" do
220
+ describe "returning" do
110
221
  let(:new_topics) { Build(1, :topic_with_book) }
111
222
 
112
223
  it "imports objects with associations" do
113
224
  assert_difference "Topic.count", +1 do
114
- Topic.import new_topics, recursive: true, on_duplicate_key_update: [:updated_at], validate: false
225
+ Topic.import new_topics, recursive: true, returning: [:content], validate: false
226
+ new_topics.each do |topic|
227
+ assert_not_nil topic.id
228
+ end
229
+ end
230
+ end
231
+ end
232
+
233
+ # If no returning option is provided, it is ignored
234
+ describe "no returning" do
235
+ let(:new_topics) { Build(1, :topic_with_book) }
236
+
237
+ it "is ignored and imports objects with associations" do
238
+ assert_difference "Topic.count", +1 do
239
+ Topic.import new_topics, recursive: true, no_returning: true, validate: false
115
240
  new_topics.each do |topic|
116
241
  assert_not_nil topic.id
117
242
  end