activerecord-import 2.2.0 → 2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -0
- data/README.markdown +9 -9
- data/lib/activerecord-import/active_record/adapters/mysql2_proxy_adapter.rb +8 -0
- data/lib/activerecord-import/active_record/adapters/postgresql_proxy_adapter.rb +8 -0
- data/lib/activerecord-import/active_record/adapters/sqlite3_proxy_adapter.rb +8 -0
- data/lib/activerecord-import/active_record/adapters/trilogy_proxy_adapter.rb +8 -0
- data/lib/activerecord-import/adapters/active_record_proxy_adapter.rb +19 -0
- data/lib/activerecord-import/adapters/mysql2_proxy_adapter.rb +9 -0
- data/lib/activerecord-import/adapters/mysql_adapter.rb +10 -3
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +1 -1
- data/lib/activerecord-import/adapters/postgresql_proxy_adapter.rb +9 -0
- data/lib/activerecord-import/adapters/sqlite3_proxy_adapter.rb +9 -0
- data/lib/activerecord-import/adapters/trilogy_proxy_adapter.rb +9 -0
- data/lib/activerecord-import/base.rb +0 -2
- data/lib/activerecord-import/import.rb +243 -207
- data/lib/activerecord-import/version.rb +1 -1
- metadata +13 -208
- data/.github/workflows/test.yaml +0 -165
- data/.gitignore +0 -37
- data/.rubocop.yml +0 -118
- data/.rubocop_todo.yml +0 -30
- data/Brewfile +0 -5
- data/Dockerfile +0 -23
- data/Gemfile +0 -63
- data/Rakefile +0 -75
- data/activerecord-import.gemspec +0 -28
- data/benchmarks/README +0 -32
- data/benchmarks/benchmark.rb +0 -72
- data/benchmarks/lib/base.rb +0 -143
- data/benchmarks/lib/cli_parser.rb +0 -111
- data/benchmarks/lib/float.rb +0 -17
- data/benchmarks/lib/mysql2_benchmark.rb +0 -21
- data/benchmarks/lib/output_to_csv.rb +0 -21
- data/benchmarks/lib/output_to_html.rb +0 -66
- data/benchmarks/models/test_innodb.rb +0 -5
- data/benchmarks/models/test_memory.rb +0 -5
- data/benchmarks/models/test_myisam.rb +0 -5
- data/benchmarks/schema/mysql2_schema.rb +0 -18
- data/docker-compose.yml +0 -34
- data/gemfiles/5.2.gemfile +0 -4
- data/gemfiles/6.0.gemfile +0 -4
- data/gemfiles/6.1.gemfile +0 -4
- data/gemfiles/7.0.gemfile +0 -4
- data/gemfiles/7.1.gemfile +0 -3
- data/gemfiles/7.2.gemfile +0 -3
- data/gemfiles/8.0.gemfile +0 -3
- data/test/adapters/janus_mysql2.rb +0 -3
- data/test/adapters/janus_trilogy.rb +0 -11
- data/test/adapters/jdbcmysql.rb +0 -3
- data/test/adapters/jdbcpostgresql.rb +0 -3
- data/test/adapters/jdbcsqlite3.rb +0 -3
- data/test/adapters/makara_postgis.rb +0 -3
- data/test/adapters/mysql2.rb +0 -3
- data/test/adapters/mysql2_makara.rb +0 -3
- data/test/adapters/mysql2_proxy.rb +0 -3
- data/test/adapters/mysql2spatial.rb +0 -3
- data/test/adapters/postgis.rb +0 -3
- data/test/adapters/postgresql.rb +0 -3
- data/test/adapters/postgresql_makara.rb +0 -3
- data/test/adapters/postgresql_proxy.rb +0 -3
- data/test/adapters/seamless_database_pool.rb +0 -3
- data/test/adapters/spatialite.rb +0 -3
- data/test/adapters/sqlite3.rb +0 -3
- data/test/adapters/trilogy.rb +0 -11
- data/test/database.yml.sample +0 -73
- data/test/github/database.yml +0 -85
- data/test/import_test.rb +0 -970
- data/test/janus_mysql2/import_test.rb +0 -8
- data/test/janus_trilogy/import_test.rb +0 -7
- data/test/jdbcmysql/import_test.rb +0 -7
- data/test/jdbcpostgresql/import_test.rb +0 -6
- data/test/jdbcsqlite3/import_test.rb +0 -6
- data/test/makara_postgis/import_test.rb +0 -10
- data/test/models/account.rb +0 -5
- data/test/models/alarm.rb +0 -4
- data/test/models/animal.rb +0 -8
- data/test/models/author.rb +0 -9
- data/test/models/bike_maker.rb +0 -10
- data/test/models/book.rb +0 -18
- data/test/models/car.rb +0 -5
- data/test/models/card.rb +0 -5
- data/test/models/chapter.rb +0 -6
- data/test/models/composite_book.rb +0 -19
- data/test/models/composite_chapter.rb +0 -12
- data/test/models/customer.rb +0 -18
- data/test/models/deck.rb +0 -8
- data/test/models/dictionary.rb +0 -6
- data/test/models/discount.rb +0 -5
- data/test/models/end_note.rb +0 -6
- data/test/models/group.rb +0 -5
- data/test/models/order.rb +0 -17
- data/test/models/playing_card.rb +0 -4
- data/test/models/promotion.rb +0 -5
- data/test/models/question.rb +0 -5
- data/test/models/rule.rb +0 -5
- data/test/models/tag.rb +0 -12
- data/test/models/tag_alias.rb +0 -11
- data/test/models/topic.rb +0 -31
- data/test/models/user.rb +0 -5
- data/test/models/user_token.rb +0 -6
- data/test/models/vendor.rb +0 -9
- data/test/models/widget.rb +0 -33
- data/test/mysql2/import_test.rb +0 -7
- data/test/mysql2_makara/import_test.rb +0 -8
- data/test/mysql2_proxy/import_test.rb +0 -6
- data/test/mysqlspatial2/import_test.rb +0 -8
- data/test/postgis/import_test.rb +0 -10
- data/test/postgresql/import_test.rb +0 -6
- data/test/postgresql_proxy/import_test.rb +0 -6
- data/test/schema/generic_schema.rb +0 -230
- data/test/schema/jdbcpostgresql_schema.rb +0 -3
- data/test/schema/mysql2_schema.rb +0 -21
- data/test/schema/postgis_schema.rb +0 -3
- data/test/schema/postgresql_schema.rb +0 -94
- data/test/schema/sqlite3_schema.rb +0 -15
- data/test/schema/version.rb +0 -12
- data/test/sqlite3/import_test.rb +0 -6
- data/test/support/active_support/test_case_extensions.rb +0 -73
- data/test/support/assertions.rb +0 -75
- data/test/support/factories.rb +0 -66
- data/test/support/generate.rb +0 -31
- data/test/support/mysql/import_examples.rb +0 -97
- data/test/support/postgresql/import_examples.rb +0 -642
- data/test/support/shared_examples/on_duplicate_key_ignore.rb +0 -45
- data/test/support/shared_examples/on_duplicate_key_update.rb +0 -437
- data/test/support/shared_examples/recursive_import.rb +0 -352
- data/test/support/sqlite3/import_examples.rb +0 -232
- data/test/synchronize_test.rb +0 -43
- data/test/test_helper.rb +0 -91
- data/test/trilogy/import_test.rb +0 -7
- data/test/value_sets_bytes_parser_test.rb +0 -106
- data/test/value_sets_records_parser_test.rb +0 -34
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
def should_support_recursive_import
|
|
4
|
-
describe "importing objects with associations" do
|
|
5
|
-
let(:new_topics) { Build(num_topics, :topic_with_book) }
|
|
6
|
-
let(:new_topics_with_invalid_chapter) do
|
|
7
|
-
chapter = new_topics.first.books.first.chapters.first
|
|
8
|
-
chapter.title = nil
|
|
9
|
-
new_topics
|
|
10
|
-
end
|
|
11
|
-
let(:num_topics) { 3 }
|
|
12
|
-
let(:num_books) { 6 }
|
|
13
|
-
let(:num_chapters) { 18 }
|
|
14
|
-
let(:num_endnotes) { 24 }
|
|
15
|
-
|
|
16
|
-
let(:new_question_with_rule) { FactoryBot.build :question, :with_rule }
|
|
17
|
-
|
|
18
|
-
it 'imports top level' do
|
|
19
|
-
assert_difference "Topic.count", +num_topics do
|
|
20
|
-
Topic.import new_topics, recursive: true
|
|
21
|
-
new_topics.each do |topic|
|
|
22
|
-
assert_not_nil topic.id
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
it 'imports first level associations' do
|
|
28
|
-
assert_difference "Book.count", +num_books do
|
|
29
|
-
Topic.import new_topics, recursive: true
|
|
30
|
-
new_topics.each do |topic|
|
|
31
|
-
topic.books.each do |book|
|
|
32
|
-
assert_equal topic.id, book.topic_id
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
it 'imports polymorphic associations' do
|
|
39
|
-
discounts = Array.new(1) { |i| Discount.new(amount: i) }
|
|
40
|
-
books = Array.new(1) { |i| Book.new(author_name: "Author ##{i}", title: "Book ##{i}") }
|
|
41
|
-
books.each do |book|
|
|
42
|
-
book.discounts << discounts
|
|
43
|
-
end
|
|
44
|
-
Book.import books, recursive: true
|
|
45
|
-
books.each do |book|
|
|
46
|
-
book.discounts.each do |discount|
|
|
47
|
-
assert_not_nil discount.discountable_id
|
|
48
|
-
assert_equal 'Book', discount.discountable_type
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
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
|
-
|
|
69
|
-
[{ recursive: false }, {}].each do |import_options|
|
|
70
|
-
it "skips recursion for #{import_options}" do
|
|
71
|
-
assert_difference "Book.count", 0 do
|
|
72
|
-
Topic.import new_topics, import_options
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
it 'imports deeper nested associations' do
|
|
78
|
-
assert_difference "Chapter.count", +num_chapters do
|
|
79
|
-
assert_difference "EndNote.count", +num_endnotes do
|
|
80
|
-
Topic.import new_topics, recursive: true
|
|
81
|
-
new_topics.each do |topic|
|
|
82
|
-
topic.books.each do |book|
|
|
83
|
-
book.chapters.each do |chapter|
|
|
84
|
-
assert_equal book.id, chapter.book_id
|
|
85
|
-
end
|
|
86
|
-
book.end_notes.each do |endnote|
|
|
87
|
-
assert_equal book.id, endnote.book_id
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
end
|
|
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
|
-
|
|
108
|
-
it "skips validation of the associations if requested" do
|
|
109
|
-
assert_difference "Chapter.count", +num_chapters do
|
|
110
|
-
Topic.import new_topics_with_invalid_chapter, validate: false, recursive: true
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
it 'imports has_one associations' do
|
|
115
|
-
assert_difference 'Rule.count' do
|
|
116
|
-
Question.import [new_question_with_rule], recursive: true
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
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_nil 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
|
-
|
|
169
|
-
if ENV['AR_VERSION'].to_f >= 7.1
|
|
170
|
-
it "should import models with auto-incrementing ID successfully with recursive set to true" do
|
|
171
|
-
author = Author.create!(name: "Foo Barson")
|
|
172
|
-
books = []
|
|
173
|
-
2.times do |i|
|
|
174
|
-
books << CompositeBook.new(author_id: author.id, title: "Book #{i}", composite_chapters: [
|
|
175
|
-
CompositeChapter.new(title: "Book #{i} composite chapter 1"),
|
|
176
|
-
CompositeChapter.new(title: "Book #{i} composite chapter 2"),
|
|
177
|
-
])
|
|
178
|
-
end
|
|
179
|
-
assert_difference "CompositeBook.count", +2 do
|
|
180
|
-
assert_difference "CompositeChapter.count", +4 do
|
|
181
|
-
CompositeBook.import books, recursive: true
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
describe "all_or_none" do
|
|
190
|
-
[Book, Chapter, Topic, EndNote].each do |type|
|
|
191
|
-
it "creates #{type}" do
|
|
192
|
-
assert_difference "#{type}.count", 0 do
|
|
193
|
-
Topic.import new_topics_with_invalid_chapter, all_or_none: true, recursive: true
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
# 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
|
|
200
|
-
# columns, keys, etc isn't generated for child associations when doing recursive import
|
|
201
|
-
if ActiveRecord::Base.connection.supports_on_duplicate_key_update?
|
|
202
|
-
describe "on_duplicate_key_update" do
|
|
203
|
-
let(:new_topics) { Build(1, :topic_with_book) }
|
|
204
|
-
|
|
205
|
-
it "imports objects with associations" do
|
|
206
|
-
assert_difference "Topic.count", +1 do
|
|
207
|
-
Topic.import new_topics, recursive: true, on_duplicate_key_update: [:updated_at], validate: false
|
|
208
|
-
new_topics.each do |topic|
|
|
209
|
-
assert_not_nil topic.id
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
context "when :all fields are updated" do
|
|
215
|
-
setup do
|
|
216
|
-
Topic.import new_topics, recursive: true
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
it "updates associated objects" do
|
|
220
|
-
new_author_name = 'Richard Bachman'
|
|
221
|
-
topic = new_topics.first
|
|
222
|
-
topic.books.each do |book|
|
|
223
|
-
book.author_name = new_author_name
|
|
224
|
-
end
|
|
225
|
-
assert_nothing_raised do
|
|
226
|
-
Topic.import new_topics, recursive: true, on_duplicate_key_update: :all
|
|
227
|
-
end
|
|
228
|
-
Topic.find(topic.id).books.each do |book|
|
|
229
|
-
assert_equal new_author_name, book.author_name
|
|
230
|
-
end
|
|
231
|
-
end
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
describe "recursive_on_duplicate_key_update" do
|
|
236
|
-
let(:new_topics) { Build(1, :topic_with_book) }
|
|
237
|
-
|
|
238
|
-
setup do
|
|
239
|
-
Topic.import new_topics, recursive: true
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
it "updates associated objects" do
|
|
243
|
-
new_author_name = 'Richard Bachman'
|
|
244
|
-
topic = new_topics.first
|
|
245
|
-
topic.books.each do |book|
|
|
246
|
-
book.author_name = new_author_name
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
assert_nothing_raised do
|
|
250
|
-
Topic.import new_topics,
|
|
251
|
-
recursive: true,
|
|
252
|
-
on_duplicate_key_update: [:id],
|
|
253
|
-
recursive_on_duplicate_key_update: {
|
|
254
|
-
books: { conflict_target: [:id], columns: [:author_name] }
|
|
255
|
-
}
|
|
256
|
-
end
|
|
257
|
-
Topic.find(topic.id).books.each do |book|
|
|
258
|
-
assert_equal new_author_name, book.author_name
|
|
259
|
-
end
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
it "updates nested associated objects" do
|
|
263
|
-
new_chapter_title = 'The Final Chapter'
|
|
264
|
-
book = new_topics.first.books.first
|
|
265
|
-
book.author_name = 'Richard Bachman'
|
|
266
|
-
|
|
267
|
-
example_chapter = book.chapters.first
|
|
268
|
-
example_chapter.title = new_chapter_title
|
|
269
|
-
|
|
270
|
-
assert_nothing_raised do
|
|
271
|
-
Topic.import new_topics,
|
|
272
|
-
recursive: true,
|
|
273
|
-
on_duplicate_key_update: [:id],
|
|
274
|
-
recursive_on_duplicate_key_update: {
|
|
275
|
-
books: { conflict_target: [:id], columns: [:author_name] },
|
|
276
|
-
chapters: { conflict_target: [:id], columns: [:title] }
|
|
277
|
-
}
|
|
278
|
-
end
|
|
279
|
-
assert_equal new_chapter_title, Chapter.find(example_chapter.id).title
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
context "when a non-standard association name is used" do
|
|
283
|
-
let(:new_topics) do
|
|
284
|
-
topic = Build(:topic)
|
|
285
|
-
|
|
286
|
-
2.times do
|
|
287
|
-
novel = topic.novels.build(title: FactoryBot.generate(:book_title), author_name: 'Stephen King')
|
|
288
|
-
3.times do
|
|
289
|
-
novel.chapters.build(title: FactoryBot.generate(:chapter_title))
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
4.times do
|
|
293
|
-
novel.end_notes.build(note: FactoryBot.generate(:end_note))
|
|
294
|
-
end
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
[topic]
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
it "updates nested associated objects" do
|
|
301
|
-
new_chapter_title = 'The Final Chapter'
|
|
302
|
-
novel = new_topics.first.novels.first
|
|
303
|
-
novel.author_name = 'Richard Bachman'
|
|
304
|
-
|
|
305
|
-
example_chapter = novel.chapters.first
|
|
306
|
-
example_chapter.title = new_chapter_title
|
|
307
|
-
|
|
308
|
-
assert_nothing_raised do
|
|
309
|
-
Topic.import new_topics,
|
|
310
|
-
recursive: true,
|
|
311
|
-
on_duplicate_key_update: [:id],
|
|
312
|
-
recursive_on_duplicate_key_update: {
|
|
313
|
-
novels: { conflict_target: [:id], columns: [:author_name] },
|
|
314
|
-
chapters: { conflict_target: [:id], columns: [:title] }
|
|
315
|
-
}
|
|
316
|
-
end
|
|
317
|
-
assert_equal new_chapter_title, Chapter.find(example_chapter.id).title
|
|
318
|
-
end
|
|
319
|
-
end
|
|
320
|
-
end
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
# If returning option is provided, it is only applied to top level models so that SQL with invalid
|
|
324
|
-
# columns, keys, etc isn't generated for child associations when doing recursive import
|
|
325
|
-
describe "returning" do
|
|
326
|
-
let(:new_topics) { Build(1, :topic_with_book) }
|
|
327
|
-
|
|
328
|
-
it "imports objects with associations" do
|
|
329
|
-
assert_difference "Topic.count", +1 do
|
|
330
|
-
Topic.import new_topics, recursive: true, returning: [:content], validate: false
|
|
331
|
-
new_topics.each do |topic|
|
|
332
|
-
assert_not_nil topic.id
|
|
333
|
-
end
|
|
334
|
-
end
|
|
335
|
-
end
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
# If no returning option is provided, it is ignored
|
|
339
|
-
describe "no returning" do
|
|
340
|
-
let(:new_topics) { Build(1, :topic_with_book) }
|
|
341
|
-
|
|
342
|
-
it "is ignored and imports objects with associations" do
|
|
343
|
-
assert_difference "Topic.count", +1 do
|
|
344
|
-
Topic.import new_topics, recursive: true, no_returning: true, validate: false
|
|
345
|
-
new_topics.each do |topic|
|
|
346
|
-
assert_not_nil topic.id
|
|
347
|
-
end
|
|
348
|
-
end
|
|
349
|
-
end
|
|
350
|
-
end
|
|
351
|
-
end
|
|
352
|
-
end
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
def should_support_sqlite3_import_functionality
|
|
4
|
-
if ActiveRecord::Base.connection.supports_on_duplicate_key_update?
|
|
5
|
-
should_support_sqlite_upsert_functionality
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
describe "#supports_imports?" do
|
|
9
|
-
it "should support import" do
|
|
10
|
-
assert ActiveRecord::Base.supports_import?
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
describe "#import" do
|
|
15
|
-
it "imports with a single insert on SQLite 3.7.11 or higher" do
|
|
16
|
-
assert_difference "Topic.count", +507 do
|
|
17
|
-
result = Topic.import Build(7, :topics)
|
|
18
|
-
assert_equal 1, result.num_inserts, "Failed to issue a single INSERT statement. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
|
|
19
|
-
assert_equal 7, Topic.count, "Failed to insert all records. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
|
|
20
|
-
|
|
21
|
-
result = Topic.import Build(500, :topics)
|
|
22
|
-
assert_equal 1, result.num_inserts, "Failed to issue a single INSERT statement. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
|
|
23
|
-
assert_equal 507, Topic.count, "Failed to insert all records. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
it "imports with a two inserts on SQLite 3.7.11 or higher" do
|
|
28
|
-
assert_difference "Topic.count", +501 do
|
|
29
|
-
result = Topic.import Build(501, :topics)
|
|
30
|
-
assert_equal 2, result.num_inserts, "Failed to issue a two INSERT statements. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
|
|
31
|
-
assert_equal 501, Topic.count, "Failed to insert all records. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
it "imports with a five inserts on SQLite 3.7.11 or higher" do
|
|
36
|
-
assert_difference "Topic.count", +2500 do
|
|
37
|
-
result = Topic.import Build(2500, :topics)
|
|
38
|
-
assert_equal 5, result.num_inserts, "Failed to issue a two INSERT statements. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
|
|
39
|
-
assert_equal 2500, Topic.count, "Failed to insert all records. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def should_support_sqlite_upsert_functionality
|
|
46
|
-
should_support_basic_on_duplicate_key_update
|
|
47
|
-
should_support_on_duplicate_key_ignore
|
|
48
|
-
|
|
49
|
-
describe "#import" do
|
|
50
|
-
extend ActiveSupport::TestCase::ImportAssertions
|
|
51
|
-
|
|
52
|
-
macro(:perform_import) { raise "supply your own #perform_import in a context below" }
|
|
53
|
-
macro(:updated_topic) { Topic.find(@topic.id) }
|
|
54
|
-
|
|
55
|
-
context "with :on_duplicate_key_ignore and validation checks turned off" do
|
|
56
|
-
let(:columns) { %w( id title author_name author_email_address parent_id ) }
|
|
57
|
-
let(:values) { [[99, "Book", "John Doe", "john@doe.com", 17]] }
|
|
58
|
-
let(:updated_values) { [[99, "Book - 2nd Edition", "Author Should Not Change", "johndoe@example.com", 57]] }
|
|
59
|
-
|
|
60
|
-
setup do
|
|
61
|
-
Topic.import columns, values, validate: false
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
it "should not update any records" do
|
|
65
|
-
result = Topic.import columns, updated_values, on_duplicate_key_ignore: true, validate: false
|
|
66
|
-
assert_equal [], result.ids
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
context "with :on_duplicate_key_update and validation checks turned off" do
|
|
71
|
-
asssertion_group(:should_support_on_duplicate_key_update) do
|
|
72
|
-
should_not_update_fields_not_mentioned
|
|
73
|
-
should_update_foreign_keys
|
|
74
|
-
should_not_update_created_at_on_timestamp_columns
|
|
75
|
-
should_update_updated_at_on_timestamp_columns
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
context "using a hash" do
|
|
79
|
-
context "with :columns a hash" do
|
|
80
|
-
let(:columns) { %w( id title author_name author_email_address parent_id ) }
|
|
81
|
-
let(:values) { [[99, "Book", "John Doe", "john@doe.com", 17]] }
|
|
82
|
-
let(:updated_values) { [[99, "Book - 2nd Edition", "Author Should Not Change", "johndoe@example.com", 57]] }
|
|
83
|
-
|
|
84
|
-
macro(:perform_import) do |*opts|
|
|
85
|
-
Topic.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: { conflict_target: :id, columns: update_columns }, validate: false)
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
setup do
|
|
89
|
-
Topic.import columns, values, validate: false
|
|
90
|
-
@topic = Topic.find 99
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
it "should not modify the passed in :on_duplicate_key_update columns array" do
|
|
94
|
-
assert_nothing_raised do
|
|
95
|
-
columns = %w(title author_name).freeze
|
|
96
|
-
Topic.import columns, [%w(foo, bar)], on_duplicate_key_update: { columns: columns }
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
context "using string hash map" do
|
|
101
|
-
let(:update_columns) { { "title" => "title", "author_email_address" => "author_email_address", "parent_id" => "parent_id" } }
|
|
102
|
-
should_support_on_duplicate_key_update
|
|
103
|
-
should_update_fields_mentioned
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
context "using string hash map, but specifying column mismatches" do
|
|
107
|
-
let(:update_columns) { { "title" => "author_email_address", "author_email_address" => "title", "parent_id" => "parent_id" } }
|
|
108
|
-
should_support_on_duplicate_key_update
|
|
109
|
-
should_update_fields_mentioned_with_hash_mappings
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
context "using symbol hash map" do
|
|
113
|
-
let(:update_columns) { { title: :title, author_email_address: :author_email_address, parent_id: :parent_id } }
|
|
114
|
-
should_support_on_duplicate_key_update
|
|
115
|
-
should_update_fields_mentioned
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
context "using symbol hash map, but specifying column mismatches" do
|
|
119
|
-
let(:update_columns) { { title: :author_email_address, author_email_address: :title, parent_id: :parent_id } }
|
|
120
|
-
should_support_on_duplicate_key_update
|
|
121
|
-
should_update_fields_mentioned_with_hash_mappings
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
context 'with :index_predicate' do
|
|
126
|
-
let(:columns) { %w( id device_id alarm_type status metadata ) }
|
|
127
|
-
let(:values) { [[99, 17, 1, 1, 'foo']] }
|
|
128
|
-
let(:updated_values) { [[99, 17, 1, 2, 'bar']] }
|
|
129
|
-
|
|
130
|
-
macro(:perform_import) do |*opts|
|
|
131
|
-
Alarm.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: { conflict_target: [:device_id, :alarm_type], index_predicate: 'status <> 0', columns: [:status] }, validate: false)
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
macro(:updated_alarm) { Alarm.find(@alarm.id) }
|
|
135
|
-
|
|
136
|
-
setup do
|
|
137
|
-
Alarm.import columns, values, validate: false
|
|
138
|
-
@alarm = Alarm.find 99
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
context 'supports on duplicate key update for partial indexes' do
|
|
142
|
-
it 'should not update created_at timestamp columns' do
|
|
143
|
-
Timecop.freeze Chronic.parse("5 minutes from now") do
|
|
144
|
-
perform_import
|
|
145
|
-
assert_in_delta @alarm.created_at.to_i, updated_alarm.created_at.to_i, 1
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
it 'should update updated_at timestamp columns' do
|
|
150
|
-
time = Chronic.parse("5 minutes from now")
|
|
151
|
-
Timecop.freeze time do
|
|
152
|
-
perform_import
|
|
153
|
-
assert_in_delta time.to_i, updated_alarm.updated_at.to_i, 1
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
it 'should not update fields not mentioned' do
|
|
158
|
-
perform_import
|
|
159
|
-
assert_equal 'foo', updated_alarm.metadata
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
it 'should update fields mentioned with hash mappings' do
|
|
163
|
-
perform_import
|
|
164
|
-
assert_equal 2, updated_alarm.status
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
context 'with :condition' do
|
|
170
|
-
let(:columns) { %w( id device_id alarm_type status metadata) }
|
|
171
|
-
let(:values) { [[99, 17, 1, 1, 'foo']] }
|
|
172
|
-
let(:updated_values) { [[99, 17, 1, 1, 'bar']] }
|
|
173
|
-
|
|
174
|
-
macro(:perform_import) do |*opts|
|
|
175
|
-
Alarm.import(
|
|
176
|
-
columns,
|
|
177
|
-
updated_values,
|
|
178
|
-
opts.extract_options!.merge(
|
|
179
|
-
on_duplicate_key_update: {
|
|
180
|
-
conflict_target: [:id],
|
|
181
|
-
condition: "alarms.metadata NOT LIKE '%foo%'",
|
|
182
|
-
columns: [:metadata]
|
|
183
|
-
},
|
|
184
|
-
validate: false
|
|
185
|
-
)
|
|
186
|
-
)
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
macro(:updated_alarm) { Alarm.find(@alarm.id) }
|
|
190
|
-
|
|
191
|
-
setup do
|
|
192
|
-
Alarm.import columns, values, validate: false
|
|
193
|
-
@alarm = Alarm.find 99
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
it 'should not update fields not matched' do
|
|
197
|
-
perform_import
|
|
198
|
-
assert_equal 'foo', updated_alarm.metadata
|
|
199
|
-
end
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
context "with no :conflict_target" do
|
|
203
|
-
context "with no primary key" do
|
|
204
|
-
it "raises ArgumentError" do
|
|
205
|
-
error = assert_raises ArgumentError do
|
|
206
|
-
Rule.import Build(3, :rules), on_duplicate_key_update: [:condition_text], validate: false
|
|
207
|
-
end
|
|
208
|
-
assert_match(/Expected :conflict_target to be specified/, error.message)
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
context "with no :columns" do
|
|
214
|
-
let(:columns) { %w( id title author_name author_email_address ) }
|
|
215
|
-
let(:values) { [[100, "Book", "John Doe", "john@doe.com"]] }
|
|
216
|
-
let(:updated_values) { [[100, "Title Should Not Change", "Author Should Not Change", "john@nogo.com"]] }
|
|
217
|
-
|
|
218
|
-
macro(:perform_import) do |*opts|
|
|
219
|
-
Topic.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: { conflict_target: :id }, validate: false)
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
setup do
|
|
223
|
-
Topic.import columns, values, validate: false
|
|
224
|
-
@topic = Topic.find 100
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
should_update_updated_at_on_timestamp_columns
|
|
228
|
-
end
|
|
229
|
-
end
|
|
230
|
-
end
|
|
231
|
-
end
|
|
232
|
-
end
|
data/test/synchronize_test.rb
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require File.expand_path('../test_helper', __FILE__)
|
|
4
|
-
|
|
5
|
-
describe ".synchronize" do
|
|
6
|
-
let(:topics) { Generate(3, :topics) }
|
|
7
|
-
let(:titles) { %w(one two three) }
|
|
8
|
-
|
|
9
|
-
setup do
|
|
10
|
-
# update records outside of ActiveRecord knowing about it
|
|
11
|
-
Topic.connection.execute( "UPDATE #{Topic.table_name} SET title='#{titles[0]}_haha' WHERE id=#{topics[0].id}", "Updating record 1 without ActiveRecord" )
|
|
12
|
-
Topic.connection.execute( "UPDATE #{Topic.table_name} SET title='#{titles[1]}_haha' WHERE id=#{topics[1].id}", "Updating record 2 without ActiveRecord" )
|
|
13
|
-
Topic.connection.execute( "UPDATE #{Topic.table_name} SET title='#{titles[2]}_haha' WHERE id=#{topics[2].id}", "Updating record 3 without ActiveRecord" )
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
it "reloads data for the specified records" do
|
|
17
|
-
Topic.synchronize topics
|
|
18
|
-
|
|
19
|
-
actual_titles = topics.map(&:title)
|
|
20
|
-
assert_equal "#{titles[0]}_haha", actual_titles[0], "the first record was not correctly updated"
|
|
21
|
-
assert_equal "#{titles[1]}_haha", actual_titles[1], "the second record was not correctly updated"
|
|
22
|
-
assert_equal "#{titles[2]}_haha", actual_titles[2], "the third record was not correctly updated"
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
it "the synchronized records aren't dirty" do
|
|
26
|
-
# Update the in memory records so they're dirty
|
|
27
|
-
topics.each { |topic| topic.title = 'dirty title' }
|
|
28
|
-
|
|
29
|
-
Topic.synchronize topics
|
|
30
|
-
|
|
31
|
-
assert_equal false, topics[0].changed?, "the first record was dirty"
|
|
32
|
-
assert_equal false, topics[1].changed?, "the second record was dirty"
|
|
33
|
-
assert_equal false, topics[2].changed?, "the third record was dirty"
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
it "ignores default scope" do
|
|
37
|
-
# update records outside of ActiveRecord knowing about it
|
|
38
|
-
Topic.connection.execute( "UPDATE #{Topic.table_name} SET approved='0' WHERE id=#{topics[0].id}", "Updating record 1 without ActiveRecord" )
|
|
39
|
-
|
|
40
|
-
Topic.synchronize topics
|
|
41
|
-
assert_equal false, topics[0].approved
|
|
42
|
-
end
|
|
43
|
-
end
|