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.
- data/.gitignore +32 -0
- data/.rubocop.yml +49 -0
- data/.rubocop_todo.yml +36 -0
- data/.travis.yml +52 -0
- data/Brewfile +3 -0
- data/CHANGELOG.md +87 -0
- data/Gemfile +54 -0
- data/LICENSE +56 -0
- data/README.markdown +101 -0
- data/Rakefile +66 -0
- data/activerecord-import.gemspec +23 -0
- data/benchmarks/README +32 -0
- data/benchmarks/benchmark.rb +67 -0
- data/benchmarks/lib/base.rb +138 -0
- data/benchmarks/lib/cli_parser.rb +106 -0
- data/benchmarks/lib/float.rb +15 -0
- data/benchmarks/lib/mysql2_benchmark.rb +19 -0
- data/benchmarks/lib/output_to_csv.rb +19 -0
- data/benchmarks/lib/output_to_html.rb +64 -0
- data/benchmarks/models/test_innodb.rb +3 -0
- data/benchmarks/models/test_memory.rb +3 -0
- data/benchmarks/models/test_myisam.rb +3 -0
- data/benchmarks/schema/mysql_schema.rb +16 -0
- data/gemfiles/3.2.gemfile +3 -0
- data/gemfiles/4.0.gemfile +3 -0
- data/gemfiles/4.1.gemfile +3 -0
- data/gemfiles/4.2.gemfile +7 -0
- data/gemfiles/5.0.gemfile +3 -0
- data/lib/activerecord-import.rb +19 -0
- data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +9 -0
- data/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +6 -0
- data/lib/activerecord-import/active_record/adapters/jdbcpostgresql_adapter.rb +6 -0
- data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +6 -0
- data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +6 -0
- data/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb +7 -0
- data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +6 -0
- data/lib/activerecord-import/adapters/abstract_adapter.rb +78 -0
- data/lib/activerecord-import/adapters/em_mysql2_adapter.rb +5 -0
- data/lib/activerecord-import/adapters/mysql2_adapter.rb +5 -0
- data/lib/activerecord-import/adapters/mysql_adapter.rb +114 -0
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +144 -0
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +51 -0
- data/lib/activerecord-import/base.rb +38 -0
- data/lib/activerecord-import/import.rb +660 -0
- data/lib/activerecord-import/mysql2.rb +7 -0
- data/lib/activerecord-import/postgresql.rb +7 -0
- data/lib/activerecord-import/sqlite3.rb +7 -0
- data/lib/activerecord-import/synchronize.rb +66 -0
- data/lib/activerecord-import/value_sets_parser.rb +55 -0
- data/lib/activerecord-import/version.rb +5 -0
- data/test/adapters/jdbcmysql.rb +1 -0
- data/test/adapters/jdbcpostgresql.rb +1 -0
- data/test/adapters/mysql2.rb +1 -0
- data/test/adapters/mysql2_makara.rb +1 -0
- data/test/adapters/mysql2spatial.rb +1 -0
- data/test/adapters/postgis.rb +1 -0
- data/test/adapters/postgresql.rb +1 -0
- data/test/adapters/postgresql_makara.rb +1 -0
- data/test/adapters/seamless_database_pool.rb +1 -0
- data/test/adapters/spatialite.rb +1 -0
- data/test/adapters/sqlite3.rb +1 -0
- data/test/database.yml.sample +52 -0
- data/test/import_test.rb +574 -0
- data/test/jdbcmysql/import_test.rb +6 -0
- data/test/jdbcpostgresql/import_test.rb +5 -0
- data/test/models/book.rb +7 -0
- data/test/models/chapter.rb +4 -0
- data/test/models/discount.rb +3 -0
- data/test/models/end_note.rb +4 -0
- data/test/models/group.rb +3 -0
- data/test/models/promotion.rb +3 -0
- data/test/models/question.rb +3 -0
- data/test/models/rule.rb +3 -0
- data/test/models/topic.rb +9 -0
- data/test/models/widget.rb +24 -0
- data/test/mysql2/import_test.rb +5 -0
- data/test/mysql2_makara/import_test.rb +6 -0
- data/test/mysqlspatial2/import_test.rb +6 -0
- data/test/postgis/import_test.rb +4 -0
- data/test/postgresql/import_test.rb +8 -0
- data/test/schema/generic_schema.rb +144 -0
- data/test/schema/mysql_schema.rb +16 -0
- data/test/schema/version.rb +10 -0
- data/test/sqlite3/import_test.rb +52 -0
- data/test/support/active_support/test_case_extensions.rb +70 -0
- data/test/support/assertions.rb +73 -0
- data/test/support/factories.rb +57 -0
- data/test/support/generate.rb +29 -0
- data/test/support/mysql/import_examples.rb +85 -0
- data/test/support/postgresql/import_examples.rb +242 -0
- data/test/support/shared_examples/on_duplicate_key_update.rb +103 -0
- data/test/support/shared_examples/recursive_import.rb +122 -0
- data/test/synchronize_test.rb +33 -0
- data/test/test_helper.rb +59 -0
- data/test/travis/database.yml +62 -0
- data/test/value_sets_bytes_parser_test.rb +93 -0
- data/test/value_sets_records_parser_test.rb +32 -0
- 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
|