activerecord-import-uuid 0.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 (98) hide show
  1. data/.gitignore +32 -0
  2. data/.rubocop.yml +49 -0
  3. data/.rubocop_todo.yml +36 -0
  4. data/.travis.yml +52 -0
  5. data/Brewfile +3 -0
  6. data/CHANGELOG.md +87 -0
  7. data/Gemfile +54 -0
  8. data/LICENSE +56 -0
  9. data/README.markdown +101 -0
  10. data/Rakefile +66 -0
  11. data/activerecord-import.gemspec +23 -0
  12. data/benchmarks/README +32 -0
  13. data/benchmarks/benchmark.rb +67 -0
  14. data/benchmarks/lib/base.rb +138 -0
  15. data/benchmarks/lib/cli_parser.rb +106 -0
  16. data/benchmarks/lib/float.rb +15 -0
  17. data/benchmarks/lib/mysql2_benchmark.rb +19 -0
  18. data/benchmarks/lib/output_to_csv.rb +19 -0
  19. data/benchmarks/lib/output_to_html.rb +64 -0
  20. data/benchmarks/models/test_innodb.rb +3 -0
  21. data/benchmarks/models/test_memory.rb +3 -0
  22. data/benchmarks/models/test_myisam.rb +3 -0
  23. data/benchmarks/schema/mysql_schema.rb +16 -0
  24. data/gemfiles/3.2.gemfile +3 -0
  25. data/gemfiles/4.0.gemfile +3 -0
  26. data/gemfiles/4.1.gemfile +3 -0
  27. data/gemfiles/4.2.gemfile +7 -0
  28. data/gemfiles/5.0.gemfile +3 -0
  29. data/lib/activerecord-import.rb +19 -0
  30. data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +9 -0
  31. data/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +6 -0
  32. data/lib/activerecord-import/active_record/adapters/jdbcpostgresql_adapter.rb +6 -0
  33. data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +6 -0
  34. data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +6 -0
  35. data/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb +7 -0
  36. data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +6 -0
  37. data/lib/activerecord-import/adapters/abstract_adapter.rb +78 -0
  38. data/lib/activerecord-import/adapters/em_mysql2_adapter.rb +5 -0
  39. data/lib/activerecord-import/adapters/mysql2_adapter.rb +5 -0
  40. data/lib/activerecord-import/adapters/mysql_adapter.rb +114 -0
  41. data/lib/activerecord-import/adapters/postgresql_adapter.rb +144 -0
  42. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +51 -0
  43. data/lib/activerecord-import/base.rb +38 -0
  44. data/lib/activerecord-import/import.rb +660 -0
  45. data/lib/activerecord-import/mysql2.rb +7 -0
  46. data/lib/activerecord-import/postgresql.rb +7 -0
  47. data/lib/activerecord-import/sqlite3.rb +7 -0
  48. data/lib/activerecord-import/synchronize.rb +66 -0
  49. data/lib/activerecord-import/value_sets_parser.rb +55 -0
  50. data/lib/activerecord-import/version.rb +5 -0
  51. data/test/adapters/jdbcmysql.rb +1 -0
  52. data/test/adapters/jdbcpostgresql.rb +1 -0
  53. data/test/adapters/mysql2.rb +1 -0
  54. data/test/adapters/mysql2_makara.rb +1 -0
  55. data/test/adapters/mysql2spatial.rb +1 -0
  56. data/test/adapters/postgis.rb +1 -0
  57. data/test/adapters/postgresql.rb +1 -0
  58. data/test/adapters/postgresql_makara.rb +1 -0
  59. data/test/adapters/seamless_database_pool.rb +1 -0
  60. data/test/adapters/spatialite.rb +1 -0
  61. data/test/adapters/sqlite3.rb +1 -0
  62. data/test/database.yml.sample +52 -0
  63. data/test/import_test.rb +574 -0
  64. data/test/jdbcmysql/import_test.rb +6 -0
  65. data/test/jdbcpostgresql/import_test.rb +5 -0
  66. data/test/models/book.rb +7 -0
  67. data/test/models/chapter.rb +4 -0
  68. data/test/models/discount.rb +3 -0
  69. data/test/models/end_note.rb +4 -0
  70. data/test/models/group.rb +3 -0
  71. data/test/models/promotion.rb +3 -0
  72. data/test/models/question.rb +3 -0
  73. data/test/models/rule.rb +3 -0
  74. data/test/models/topic.rb +9 -0
  75. data/test/models/widget.rb +24 -0
  76. data/test/mysql2/import_test.rb +5 -0
  77. data/test/mysql2_makara/import_test.rb +6 -0
  78. data/test/mysqlspatial2/import_test.rb +6 -0
  79. data/test/postgis/import_test.rb +4 -0
  80. data/test/postgresql/import_test.rb +8 -0
  81. data/test/schema/generic_schema.rb +144 -0
  82. data/test/schema/mysql_schema.rb +16 -0
  83. data/test/schema/version.rb +10 -0
  84. data/test/sqlite3/import_test.rb +52 -0
  85. data/test/support/active_support/test_case_extensions.rb +70 -0
  86. data/test/support/assertions.rb +73 -0
  87. data/test/support/factories.rb +57 -0
  88. data/test/support/generate.rb +29 -0
  89. data/test/support/mysql/import_examples.rb +85 -0
  90. data/test/support/postgresql/import_examples.rb +242 -0
  91. data/test/support/shared_examples/on_duplicate_key_update.rb +103 -0
  92. data/test/support/shared_examples/recursive_import.rb +122 -0
  93. data/test/synchronize_test.rb +33 -0
  94. data/test/test_helper.rb +59 -0
  95. data/test/travis/database.yml +62 -0
  96. data/test/value_sets_bytes_parser_test.rb +93 -0
  97. data/test/value_sets_records_parser_test.rb +32 -0
  98. metadata +225 -0
@@ -0,0 +1,57 @@
1
+ FactoryGirl.define do
2
+ sequence(:book_title) { |n| "Book #{n}" }
3
+ sequence(:chapter_title) { |n| "Chapter #{n}" }
4
+ sequence(:end_note) { |n| "Endnote #{n}" }
5
+
6
+ factory :group do
7
+ sequence(:order) { |n| "Order #{n}" }
8
+ end
9
+
10
+ factory :invalid_topic, class: "Topic" do
11
+ sequence(:title) { |n| "Title #{n}" }
12
+ author_name nil
13
+ end
14
+
15
+ factory :topic do
16
+ sequence(:title) { |n| "Title #{n}" }
17
+ sequence(:author_name) { |n| "Author #{n}" }
18
+ end
19
+
20
+ factory :widget do
21
+ sequence(:w_id) { |n| n }
22
+ end
23
+
24
+ factory :question do
25
+ sequence(:body) { |n| "Text #{n}" }
26
+
27
+ trait :with_rule do
28
+ after(:build) do |question|
29
+ question.build_rule(FactoryGirl.attributes_for(:rule))
30
+ end
31
+ end
32
+ end
33
+
34
+ factory :rule do
35
+ sequence(:condition_text) { |n| "q_#{n}_#{n}" }
36
+ end
37
+
38
+ factory :topic_with_book, parent: :topic do
39
+ after(:build) do |topic|
40
+ 2.times do
41
+ book = topic.books.build(title: FactoryGirl.generate(:book_title), author_name: 'Stephen King')
42
+ 3.times do
43
+ book.chapters.build(title: FactoryGirl.generate(:chapter_title))
44
+ end
45
+
46
+ 4.times do
47
+ book.end_notes.build(note: FactoryGirl.generate(:end_note))
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ factory :book do
54
+ title 'Tortilla Flat'
55
+ author_name 'John Steinbeck'
56
+ end
57
+ end
@@ -0,0 +1,29 @@
1
+ class ActiveSupport::TestCase
2
+ def Build(*args) # rubocop:disable Style/MethodName
3
+ n = args.shift if args.first.is_a?(Numeric)
4
+ factory = args.shift
5
+ factory_girl_args = args.shift || {}
6
+
7
+ if n
8
+ [].tap do |collection|
9
+ n.times.each { collection << FactoryGirl.build(factory.to_s.singularize.to_sym, factory_girl_args) }
10
+ end
11
+ else
12
+ FactoryGirl.build(factory.to_s.singularize.to_sym, factory_girl_args)
13
+ end
14
+ end
15
+
16
+ def Generate(*args) # rubocop:disable Style/MethodName
17
+ n = args.shift if args.first.is_a?(Numeric)
18
+ factory = args.shift
19
+ factory_girl_args = args.shift || {}
20
+
21
+ if n
22
+ [].tap do |collection|
23
+ n.times.each { collection << FactoryGirl.create(factory.to_s.singularize.to_sym, factory_girl_args) }
24
+ end
25
+ else
26
+ FactoryGirl.create(factory.to_s.singularize.to_sym, factory_girl_args)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,85 @@
1
+ # encoding: UTF-8
2
+ def should_support_mysql_import_functionality
3
+ # Forcefully disable strict mode for this session.
4
+ ActiveRecord::Base.connection.execute "set sql_mode='STRICT_ALL_TABLES'"
5
+
6
+ should_support_basic_on_duplicate_key_update
7
+
8
+ describe "#import" do
9
+ context "with :on_duplicate_key_update and validation checks turned off" do
10
+ extend ActiveSupport::TestCase::ImportAssertions
11
+
12
+ asssertion_group(:should_support_on_duplicate_key_update) do
13
+ should_not_update_fields_not_mentioned
14
+ should_update_foreign_keys
15
+ should_not_update_created_at_on_timestamp_columns
16
+ should_update_updated_at_on_timestamp_columns
17
+ end
18
+
19
+ macro(:perform_import) { raise "supply your own #perform_import in a context below" }
20
+ macro(:updated_topic) { Topic.find(@topic.id) }
21
+
22
+ let(:columns) { %w( id title author_name author_email_address parent_id ) }
23
+ let(:values) { [[99, "Book", "John Doe", "john@doe.com", 17]] }
24
+ let(:updated_values) { [[99, "Book - 2nd Edition", "Author Should Not Change", "johndoe@example.com", 57]] }
25
+
26
+ macro(:perform_import) do |*opts|
27
+ Topic.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: update_columns, validate: false)
28
+ end
29
+
30
+ setup do
31
+ Topic.import columns, values, validate: false
32
+ @topic = Topic.find 99
33
+ end
34
+
35
+ context "using string hash map" do
36
+ let(:update_columns) { { "title" => "title", "author_email_address" => "author_email_address", "parent_id" => "parent_id" } }
37
+ should_support_on_duplicate_key_update
38
+ should_update_fields_mentioned
39
+ end
40
+
41
+ context "using string hash map, but specifying column mismatches" do
42
+ let(:update_columns) { { "title" => "author_email_address", "author_email_address" => "title", "parent_id" => "parent_id" } }
43
+ should_support_on_duplicate_key_update
44
+ should_update_fields_mentioned_with_hash_mappings
45
+ end
46
+
47
+ context "using symbol hash map" do
48
+ let(:update_columns) { { title: :title, author_email_address: :author_email_address, parent_id: :parent_id } }
49
+ should_support_on_duplicate_key_update
50
+ should_update_fields_mentioned
51
+ end
52
+
53
+ context "using symbol hash map, but specifying column mismatches" do
54
+ let(:update_columns) { { title: :author_email_address, author_email_address: :title, parent_id: :parent_id } }
55
+ should_support_on_duplicate_key_update
56
+ should_update_fields_mentioned_with_hash_mappings
57
+ end
58
+ end
59
+
60
+ context "with :synchronization option" do
61
+ let(:topics) { [] }
62
+ let(:values) { [[topics.first.id, "Jerry Carter", "title1"], [topics.last.id, "Chad Fowler", "title2"]] }
63
+ let(:columns) { %w(id author_name title) }
64
+
65
+ setup do
66
+ topics << Topic.create!(title: "LDAP", author_name: "Big Bird")
67
+ topics << Topic.create!(title: "Rails Recipes", author_name: "Elmo")
68
+ end
69
+
70
+ it "synchronizes passed in ActiveRecord model instances with the data just imported" do
71
+ columns2update = ['author_name']
72
+
73
+ expected_count = Topic.count
74
+ Topic.import( columns, values,
75
+ validate: false,
76
+ on_duplicate_key_update: columns2update,
77
+ synchronize: topics )
78
+
79
+ assert_equal expected_count, Topic.count, "no new records should have been created!"
80
+ assert_equal "Jerry Carter", topics.first.author_name, "wrong author!"
81
+ assert_equal "Chad Fowler", topics.last.author_name, "wrong author!"
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,242 @@
1
+ # encoding: UTF-8
2
+ def should_support_postgresql_import_functionality
3
+ should_support_recursive_import
4
+
5
+ describe "#supports_imports?" do
6
+ it "should support import" do
7
+ assert ActiveRecord::Base.supports_import?
8
+ end
9
+ end
10
+
11
+ describe "#import" do
12
+ it "should import with a single insert" do
13
+ # see ActiveRecord::ConnectionAdapters::AbstractAdapter test for more specifics
14
+ assert_difference "Topic.count", +10 do
15
+ result = Topic.import Build(3, :topics)
16
+ assert_equal 1, result.num_inserts
17
+
18
+ result = Topic.import Build(7, :topics)
19
+ assert_equal 1, result.num_inserts
20
+ end
21
+ end
22
+
23
+ describe "with query cache enabled" do
24
+ setup do
25
+ unless ActiveRecord::Base.connection.query_cache_enabled
26
+ ActiveRecord::Base.connection.enable_query_cache!
27
+ @disable_cache_on_teardown = true
28
+ end
29
+ end
30
+
31
+ it "clears cache on insert" do
32
+ before_import = Topic.all.to_a
33
+
34
+ Topic.import(Build(2, :topics), validate: false)
35
+
36
+ after_import = Topic.all.to_a
37
+ assert_equal 2, after_import.size - before_import.size
38
+ end
39
+
40
+ teardown do
41
+ if @disable_cache_on_teardown
42
+ ActiveRecord::Base.connection.disable_query_cache!
43
+ end
44
+ end
45
+ end
46
+
47
+ describe "no_returning" do
48
+ let(:books) { [Book.new(author_name: "foo", title: "bar")] }
49
+
50
+ it "creates records" do
51
+ assert_difference "Book.count", +1 do
52
+ Book.import books, no_returning: true
53
+ end
54
+ end
55
+
56
+ it "returns no ids" do
57
+ assert_equal [], Book.import(books, no_returning: true).ids
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ def should_support_postgresql_upsert_functionality
64
+ should_support_basic_on_duplicate_key_update
65
+
66
+ describe "#import" do
67
+ extend ActiveSupport::TestCase::ImportAssertions
68
+
69
+ macro(:perform_import) { raise "supply your own #perform_import in a context below" }
70
+ macro(:updated_topic) { Topic.find(@topic.id) }
71
+
72
+ context "with :on_duplicate_key_ignore and validation checks turned off" do
73
+ let(:columns) { %w( id title author_name author_email_address parent_id ) }
74
+ let(:values) { [[99, "Book", "John Doe", "john@doe.com", 17]] }
75
+ let(:updated_values) { [[99, "Book - 2nd Edition", "Author Should Not Change", "johndoe@example.com", 57]] }
76
+
77
+ setup do
78
+ Topic.import columns, values, validate: false
79
+ end
80
+
81
+ it "should not update any records" do
82
+ result = Topic.import columns, updated_values, on_duplicate_key_ignore: true, validate: false
83
+ assert_equal [], result.ids
84
+ end
85
+ end
86
+
87
+ context "with :on_duplicate_key_ignore and :recursive enabled" do
88
+ let(:new_topic) { Build(1, :topic_with_book) }
89
+ let(:mixed_topics) { Build(1, :topic_with_book) + new_topic + Build(1, :topic_with_book) }
90
+
91
+ setup do
92
+ Topic.import new_topic, recursive: true
93
+ end
94
+
95
+ # Recursive import depends on the primary keys of the parent model being returned
96
+ # on insert. With on_duplicate_key_ignore enabled, not all ids will be returned
97
+ # and it is possible that a model will be assigned the wrong id and then its children
98
+ # would be associated with the wrong parent.
99
+ it ":on_duplicate_key_ignore is ignored" do
100
+ assert_raise ActiveRecord::RecordNotUnique do
101
+ Topic.import mixed_topics, recursive: true, on_duplicate_key_ignore: true
102
+ end
103
+ end
104
+ end
105
+
106
+ context "with :on_duplicate_key_update and validation checks turned off" do
107
+ asssertion_group(:should_support_on_duplicate_key_update) do
108
+ should_not_update_fields_not_mentioned
109
+ should_update_foreign_keys
110
+ should_not_update_created_at_on_timestamp_columns
111
+ should_update_updated_at_on_timestamp_columns
112
+ end
113
+
114
+ context "using a hash" do
115
+ context "with :columns a hash" do
116
+ let(:columns) { %w( id title author_name author_email_address parent_id ) }
117
+ let(:values) { [[99, "Book", "John Doe", "john@doe.com", 17]] }
118
+ let(:updated_values) { [[99, "Book - 2nd Edition", "Author Should Not Change", "johndoe@example.com", 57]] }
119
+
120
+ macro(:perform_import) do |*opts|
121
+ Topic.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: { conflict_target: :id, columns: update_columns }, validate: false)
122
+ end
123
+
124
+ setup do
125
+ Topic.import columns, values, validate: false
126
+ @topic = Topic.find 99
127
+ end
128
+
129
+ context "using string hash map" do
130
+ let(:update_columns) { { "title" => "title", "author_email_address" => "author_email_address", "parent_id" => "parent_id" } }
131
+ should_support_on_duplicate_key_update
132
+ should_update_fields_mentioned
133
+ end
134
+
135
+ context "using string hash map, but specifying column mismatches" do
136
+ let(:update_columns) { { "title" => "author_email_address", "author_email_address" => "title", "parent_id" => "parent_id" } }
137
+ should_support_on_duplicate_key_update
138
+ should_update_fields_mentioned_with_hash_mappings
139
+ end
140
+
141
+ context "using symbol hash map" do
142
+ let(:update_columns) { { title: :title, author_email_address: :author_email_address, parent_id: :parent_id } }
143
+ should_support_on_duplicate_key_update
144
+ should_update_fields_mentioned
145
+ end
146
+
147
+ context "using symbol hash map, but specifying column mismatches" do
148
+ let(:update_columns) { { title: :author_email_address, author_email_address: :title, parent_id: :parent_id } }
149
+ should_support_on_duplicate_key_update
150
+ should_update_fields_mentioned_with_hash_mappings
151
+ end
152
+ end
153
+
154
+ context "with :constraint_name" do
155
+ let(:columns) { %w( id title author_name author_email_address parent_id ) }
156
+ let(:values) { [[100, "Book", "John Doe", "john@doe.com", 17]] }
157
+ let(:updated_values) { [[100, "Book - 2nd Edition", "Author Should Not Change", "johndoe@example.com", 57]] }
158
+
159
+ macro(:perform_import) do |*opts|
160
+ Topic.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: { constraint_name: :topics_pkey, columns: update_columns }, validate: false)
161
+ end
162
+
163
+ setup do
164
+ Topic.import columns, values, validate: false
165
+ @topic = Topic.find 100
166
+ end
167
+
168
+ let(:update_columns) { [:title, :author_email_address, :parent_id] }
169
+ should_support_on_duplicate_key_update
170
+ should_update_fields_mentioned
171
+ end
172
+
173
+ context "default to the primary key" do
174
+ let(:columns) { %w( id title author_name author_email_address parent_id ) }
175
+ let(:values) { [[100, "Book", "John Doe", "john@doe.com", 17]] }
176
+ let(:updated_values) { [[100, "Book - 2nd Edition", "Author Should Not Change", "johndoe@example.com", 57]] }
177
+ let(:update_columns) { [:title, :author_email_address, :parent_id] }
178
+
179
+ setup do
180
+ Topic.import columns, values, validate: false
181
+ @topic = Topic.find 100
182
+ end
183
+
184
+ context "with no :conflict_target or :constraint_name" do
185
+ macro(:perform_import) do |*opts|
186
+ Topic.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: { columns: update_columns }, validate: false)
187
+ end
188
+
189
+ should_support_on_duplicate_key_update
190
+ should_update_fields_mentioned
191
+ end
192
+
193
+ context "with empty value for :conflict_target" do
194
+ macro(:perform_import) do |*opts|
195
+ Topic.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: { conflict_target: [], columns: update_columns }, validate: false)
196
+ end
197
+
198
+ should_support_on_duplicate_key_update
199
+ should_update_fields_mentioned
200
+ end
201
+
202
+ context "with empty value for :constraint_name" do
203
+ macro(:perform_import) do |*opts|
204
+ Topic.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: { constraint_name: '', columns: update_columns }, validate: false)
205
+ end
206
+
207
+ should_support_on_duplicate_key_update
208
+ should_update_fields_mentioned
209
+ end
210
+ end
211
+
212
+ context "with no :conflict_target or :constraint_name" do
213
+ context "with no primary key" do
214
+ it "raises ArgumentError" do
215
+ error = assert_raises ArgumentError do
216
+ Widget.import Build(1, :widgets), on_duplicate_key_update: [:data], validate: false
217
+ end
218
+ assert_match(/Expected :conflict_target or :constraint_name to be specified/, error.message)
219
+ end
220
+ end
221
+ end
222
+
223
+ context "with no :columns" do
224
+ let(:columns) { %w( id title author_name author_email_address ) }
225
+ let(:values) { [[100, "Book", "John Doe", "john@doe.com"]] }
226
+ let(:updated_values) { [[100, "Title Should Not Change", "Author Should Not Change", "john@nogo.com"]] }
227
+
228
+ macro(:perform_import) do |*opts|
229
+ Topic.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: { conflict_target: :id }, validate: false)
230
+ end
231
+
232
+ setup do
233
+ Topic.import columns, values, validate: false
234
+ @topic = Topic.find 100
235
+ end
236
+
237
+ should_update_updated_at_on_timestamp_columns
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,103 @@
1
+ def should_support_basic_on_duplicate_key_update
2
+ describe "#import" do
3
+ extend ActiveSupport::TestCase::ImportAssertions
4
+
5
+ macro(:perform_import) { raise "supply your own #perform_import in a context below" }
6
+ macro(:updated_topic) { Topic.find(@topic.id) }
7
+
8
+ context "with :on_duplicate_key_update" do
9
+ describe "argument safety" do
10
+ it "should not modify the passed in :on_duplicate_key_update columns array" do
11
+ assert_nothing_raised do
12
+ columns = %w(title author_name).freeze
13
+ Topic.import columns, [%w(foo, bar)], on_duplicate_key_update: columns
14
+ end
15
+ end
16
+ end
17
+
18
+ context "with validation checks turned off" do
19
+ asssertion_group(:should_support_on_duplicate_key_update) do
20
+ should_not_update_fields_not_mentioned
21
+ should_update_foreign_keys
22
+ should_not_update_created_at_on_timestamp_columns
23
+ should_update_updated_at_on_timestamp_columns
24
+ end
25
+
26
+ let(:columns) { %w( id title author_name author_email_address parent_id ) }
27
+ let(:values) { [[99, "Book", "John Doe", "john@doe.com", 17]] }
28
+ let(:updated_values) { [[99, "Book - 2nd Edition", "Author Should Not Change", "johndoe@example.com", 57]] }
29
+
30
+ macro(:perform_import) do |*opts|
31
+ Topic.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: update_columns, validate: false)
32
+ end
33
+
34
+ setup do
35
+ Topic.import columns, values, validate: false
36
+ @topic = Topic.find 99
37
+ end
38
+
39
+ context "using an empty array" do
40
+ let(:update_columns) { [] }
41
+ should_not_update_fields_not_mentioned
42
+ should_update_updated_at_on_timestamp_columns
43
+ end
44
+
45
+ context "using string column names" do
46
+ let(:update_columns) { %w(title author_email_address parent_id) }
47
+ should_support_on_duplicate_key_update
48
+ should_update_fields_mentioned
49
+ end
50
+
51
+ context "using symbol column names" do
52
+ let(:update_columns) { [:title, :author_email_address, :parent_id] }
53
+ should_support_on_duplicate_key_update
54
+ should_update_fields_mentioned
55
+ end
56
+ end
57
+
58
+ context "with a table that has a non-standard primary key" do
59
+ let(:columns) { [:promotion_id, :code] }
60
+ let(:values) { [[1, 'DISCOUNT1']] }
61
+ let(:updated_values) { [[1, 'DISCOUNT2']] }
62
+ let(:update_columns) { [:code] }
63
+
64
+ macro(:perform_import) do |*opts|
65
+ Promotion.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: update_columns, validate: false)
66
+ end
67
+ macro(:updated_promotion) { Promotion.find(@promotion.promotion_id) }
68
+
69
+ setup do
70
+ Promotion.import columns, values, validate: false
71
+ @promotion = Promotion.find 1
72
+ end
73
+
74
+ it "should update specified columns" do
75
+ perform_import
76
+ assert_equal 'DISCOUNT2', updated_promotion.code
77
+ end
78
+ end
79
+ end
80
+
81
+ context "with :on_duplicate_key_update turned off" do
82
+ let(:columns) { %w( id title author_name author_email_address parent_id ) }
83
+ let(:values) { [[100, "Book", "John Doe", "john@doe.com", 17]] }
84
+ let(:updated_values) { [[100, "Book - 2nd Edition", "This should raise an exception", "john@nogo.com", 57]] }
85
+
86
+ macro(:perform_import) do |*opts|
87
+ # `on_duplicate_key_update: false` is the tested feature
88
+ Topic.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: false, validate: false)
89
+ end
90
+
91
+ setup do
92
+ Topic.import columns, values, validate: false
93
+ @topic = Topic.find 100
94
+ end
95
+
96
+ it "should raise ActiveRecord::RecordNotUnique" do
97
+ assert_raise ActiveRecord::RecordNotUnique do
98
+ perform_import
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end