activerecord-import-uuid 0.1

Sign up to get free protection for your applications and to get access to all the features.
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