activerecord-import 0.17.2 → 1.1.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 +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +40 -23
- data/CHANGELOG.md +315 -1
- data/Gemfile +23 -13
- data/LICENSE +21 -56
- data/README.markdown +564 -33
- data/Rakefile +2 -1
- data/activerecord-import.gemspec +3 -3
- data/benchmarks/lib/cli_parser.rb +2 -1
- data/benchmarks/schema/{mysql_schema.rb → mysql2_schema.rb} +0 -0
- data/gemfiles/5.1.gemfile +2 -0
- data/gemfiles/5.2.gemfile +2 -0
- data/gemfiles/6.0.gemfile +2 -0
- data/gemfiles/6.1.gemfile +1 -0
- data/lib/activerecord-import.rb +2 -15
- data/lib/activerecord-import/adapters/abstract_adapter.rb +9 -3
- data/lib/activerecord-import/adapters/mysql_adapter.rb +17 -11
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +68 -20
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +128 -9
- data/lib/activerecord-import/base.rb +12 -7
- data/lib/activerecord-import/import.rb +514 -166
- data/lib/activerecord-import/synchronize.rb +2 -2
- data/lib/activerecord-import/value_sets_parser.rb +16 -0
- data/lib/activerecord-import/version.rb +1 -1
- data/test/adapters/makara_postgis.rb +1 -0
- data/test/import_test.rb +274 -23
- data/test/makara_postgis/import_test.rb +8 -0
- data/test/models/account.rb +3 -0
- data/test/models/animal.rb +6 -0
- data/test/models/bike_maker.rb +7 -0
- data/test/models/tag.rb +1 -1
- data/test/models/topic.rb +14 -0
- data/test/models/user.rb +3 -0
- data/test/models/user_token.rb +4 -0
- data/test/schema/generic_schema.rb +30 -8
- data/test/schema/mysql2_schema.rb +19 -0
- data/test/schema/postgresql_schema.rb +18 -0
- data/test/schema/sqlite3_schema.rb +13 -0
- data/test/support/factories.rb +9 -8
- data/test/support/generate.rb +6 -6
- data/test/support/mysql/import_examples.rb +14 -2
- data/test/support/postgresql/import_examples.rb +220 -1
- data/test/support/shared_examples/on_duplicate_key_ignore.rb +15 -9
- data/test/support/shared_examples/on_duplicate_key_update.rb +271 -8
- data/test/support/shared_examples/recursive_import.rb +91 -21
- data/test/support/sqlite3/import_examples.rb +189 -25
- data/test/synchronize_test.rb +8 -0
- data/test/test_helper.rb +24 -3
- data/test/value_sets_bytes_parser_test.rb +13 -2
- metadata +32 -13
- data/test/schema/mysql_schema.rb +0 -16
@@ -0,0 +1,8 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
|
3
|
+
|
4
|
+
should_support_postgresql_import_functionality
|
5
|
+
|
6
|
+
if ActiveRecord::Base.connection.supports_on_duplicate_key_update?
|
7
|
+
should_support_postgresql_upsert_functionality
|
8
|
+
end
|
data/test/models/tag.rb
CHANGED
data/test/models/topic.rb
CHANGED
@@ -1,9 +1,23 @@
|
|
1
1
|
class Topic < ActiveRecord::Base
|
2
2
|
validates_presence_of :author_name
|
3
3
|
validates :title, numericality: { only_integer: true }, on: :context_test
|
4
|
+
validates :title, uniqueness: true
|
5
|
+
validates :content, uniqueness: true
|
6
|
+
validates :word_count, numericality: { greater_than: 0 }, if: :content?
|
7
|
+
|
8
|
+
validate -> { errors.add(:title, :validate_failed) if title == 'validate_failed' }
|
9
|
+
before_validation -> { errors.add(:title, :invalid) if title == 'invalid' }
|
4
10
|
|
5
11
|
has_many :books, inverse_of: :topic
|
6
12
|
belongs_to :parent, class_name: "Topic"
|
7
13
|
|
8
14
|
composed_of :description, mapping: [%w(title title), %w(author_name author_name)], allow_nil: true, class_name: "TopicDescription"
|
15
|
+
|
16
|
+
default_scope { where(approved: true) }
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def word_count
|
21
|
+
@word_count ||= content.to_s.scan(/\w+/).count
|
22
|
+
end
|
9
23
|
end
|
data/test/models/user.rb
ADDED
@@ -159,14 +159,36 @@ ActiveRecord::Schema.define do
|
|
159
159
|
t.string :Features
|
160
160
|
end
|
161
161
|
|
162
|
+
create_table :users, force: :cascade do |t|
|
163
|
+
t.string :name, null: false
|
164
|
+
t.integer :lock_version, null: false, default: 0
|
165
|
+
end
|
166
|
+
|
167
|
+
create_table :user_tokens, force: :cascade do |t|
|
168
|
+
t.string :user_name, null: false
|
169
|
+
t.string :token, null: false
|
170
|
+
end
|
171
|
+
|
172
|
+
create_table :accounts, force: :cascade do |t|
|
173
|
+
t.string :name, null: false
|
174
|
+
t.integer :lock, null: false, default: 0
|
175
|
+
end
|
176
|
+
|
177
|
+
create_table :bike_makers, force: :cascade do |t|
|
178
|
+
t.string :name, null: false
|
179
|
+
t.integer :lock_version, null: false, default: 0
|
180
|
+
end
|
181
|
+
|
162
182
|
add_index :cars, :Name, unique: true
|
163
183
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
184
|
+
unless ENV["SKIP_COMPOSITE_PK"]
|
185
|
+
execute %(
|
186
|
+
CREATE TABLE IF NOT EXISTS tags (
|
187
|
+
tag_id INT NOT NULL,
|
188
|
+
publisher_id INT NOT NULL,
|
189
|
+
tag VARCHAR(50),
|
190
|
+
PRIMARY KEY (tag_id, publisher_id)
|
191
|
+
);
|
192
|
+
).split.join(' ').strip
|
193
|
+
end
|
172
194
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
create_table :books, force: :cascade do |t|
|
3
|
+
t.string :title, null: false
|
4
|
+
t.virtual :upper_title, type: :string, as: "upper(`title`)" if t.respond_to?(:virtual)
|
5
|
+
t.string :publisher, null: false, default: 'Default Publisher'
|
6
|
+
t.string :author_name, null: false
|
7
|
+
t.datetime :created_at
|
8
|
+
t.datetime :created_on
|
9
|
+
t.datetime :updated_at
|
10
|
+
t.datetime :updated_on
|
11
|
+
t.date :publish_date
|
12
|
+
t.integer :topic_id
|
13
|
+
t.integer :tag_id
|
14
|
+
t.integer :publisher_id
|
15
|
+
t.boolean :for_sale, default: true
|
16
|
+
t.integer :status, default: 0
|
17
|
+
t.string :type
|
18
|
+
end
|
19
|
+
end
|
@@ -1,12 +1,26 @@
|
|
1
1
|
ActiveRecord::Schema.define do
|
2
2
|
execute('CREATE extension IF NOT EXISTS "hstore";')
|
3
|
+
execute('CREATE extension IF NOT EXISTS "pgcrypto";')
|
3
4
|
execute('CREATE extension IF NOT EXISTS "uuid-ossp";')
|
4
5
|
|
6
|
+
# create ENUM if it does not exist yet
|
7
|
+
begin
|
8
|
+
execute('CREATE TYPE vendor_type AS ENUM (\'wholesaler\', \'retailer\');')
|
9
|
+
rescue ActiveRecord::StatementInvalid => e
|
10
|
+
# since PostgreSQL does not support IF NOT EXISTS when creating a TYPE,
|
11
|
+
# rescue the error and check the error class
|
12
|
+
raise unless e.cause.is_a? PG::DuplicateObject
|
13
|
+
execute('ALTER TYPE vendor_type ADD VALUE IF NOT EXISTS \'wholesaler\';')
|
14
|
+
execute('ALTER TYPE vendor_type ADD VALUE IF NOT EXISTS \'retailer\';')
|
15
|
+
end
|
16
|
+
|
5
17
|
create_table :vendors, id: :uuid, force: :cascade do |t|
|
6
18
|
t.string :name, null: true
|
19
|
+
t.text :hours
|
7
20
|
t.text :preferences
|
8
21
|
|
9
22
|
if t.respond_to?(:json)
|
23
|
+
t.json :pure_json_data
|
10
24
|
t.json :data
|
11
25
|
else
|
12
26
|
t.text :data
|
@@ -19,6 +33,7 @@ ActiveRecord::Schema.define do
|
|
19
33
|
end
|
20
34
|
|
21
35
|
if t.respond_to?(:jsonb)
|
36
|
+
t.jsonb :pure_jsonb_data
|
22
37
|
t.jsonb :settings
|
23
38
|
t.jsonb :json_data, null: false, default: {}
|
24
39
|
else
|
@@ -26,6 +41,8 @@ ActiveRecord::Schema.define do
|
|
26
41
|
t.text :json_data
|
27
42
|
end
|
28
43
|
|
44
|
+
t.column :vendor_type, :vendor_type
|
45
|
+
|
29
46
|
t.datetime :created_at
|
30
47
|
t.datetime :updated_at
|
31
48
|
end
|
@@ -35,6 +52,7 @@ ActiveRecord::Schema.define do
|
|
35
52
|
t.column :alarm_type, :integer, null: false
|
36
53
|
t.column :status, :integer, null: false
|
37
54
|
t.column :metadata, :text
|
55
|
+
t.column :secret_key, :binary
|
38
56
|
t.datetime :created_at
|
39
57
|
t.datetime :updated_at
|
40
58
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
create_table :alarms, force: true do |t|
|
3
|
+
t.column :device_id, :integer, null: false
|
4
|
+
t.column :alarm_type, :integer, null: false
|
5
|
+
t.column :status, :integer, null: false
|
6
|
+
t.column :metadata, :text
|
7
|
+
t.column :secret_key, :binary
|
8
|
+
t.datetime :created_at
|
9
|
+
t.datetime :updated_at
|
10
|
+
end
|
11
|
+
|
12
|
+
add_index :alarms, [:device_id, :alarm_type], unique: true, where: 'status <> 0'
|
13
|
+
end
|
data/test/support/factories.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
FactoryBot.define do
|
2
2
|
sequence(:book_title) { |n| "Book #{n}" }
|
3
3
|
sequence(:chapter_title) { |n| "Chapter #{n}" }
|
4
4
|
sequence(:end_note) { |n| "Endnote #{n}" }
|
@@ -9,12 +9,13 @@ FactoryGirl.define do
|
|
9
9
|
|
10
10
|
factory :invalid_topic, class: "Topic" do
|
11
11
|
sequence(:title) { |n| "Title #{n}" }
|
12
|
-
author_name nil
|
12
|
+
author_name { nil }
|
13
13
|
end
|
14
14
|
|
15
15
|
factory :topic do
|
16
16
|
sequence(:title) { |n| "Title #{n}" }
|
17
17
|
sequence(:author_name) { |n| "Author #{n}" }
|
18
|
+
sequence(:content) { |n| "Content #{n}" }
|
18
19
|
end
|
19
20
|
|
20
21
|
factory :widget do
|
@@ -26,7 +27,7 @@ FactoryGirl.define do
|
|
26
27
|
|
27
28
|
trait :with_rule do
|
28
29
|
after(:build) do |question|
|
29
|
-
question.build_rule(
|
30
|
+
question.build_rule(FactoryBot.attributes_for(:rule))
|
30
31
|
end
|
31
32
|
end
|
32
33
|
end
|
@@ -39,21 +40,21 @@ FactoryGirl.define do
|
|
39
40
|
factory :topic_with_book, parent: :topic do
|
40
41
|
after(:build) do |topic|
|
41
42
|
2.times do
|
42
|
-
book = topic.books.build(title:
|
43
|
+
book = topic.books.build(title: FactoryBot.generate(:book_title), author_name: 'Stephen King')
|
43
44
|
3.times do
|
44
|
-
book.chapters.build(title:
|
45
|
+
book.chapters.build(title: FactoryBot.generate(:chapter_title))
|
45
46
|
end
|
46
47
|
|
47
48
|
4.times do
|
48
|
-
book.end_notes.build(note:
|
49
|
+
book.end_notes.build(note: FactoryBot.generate(:end_note))
|
49
50
|
end
|
50
51
|
end
|
51
52
|
end
|
52
53
|
end
|
53
54
|
|
54
55
|
factory :book do
|
55
|
-
title 'Tortilla Flat'
|
56
|
-
author_name 'John Steinbeck'
|
56
|
+
title { 'Tortilla Flat' }
|
57
|
+
author_name { 'John Steinbeck' }
|
57
58
|
end
|
58
59
|
|
59
60
|
factory :car do
|
data/test/support/generate.rb
CHANGED
@@ -2,28 +2,28 @@ class ActiveSupport::TestCase
|
|
2
2
|
def Build(*args) # rubocop:disable Style/MethodName
|
3
3
|
n = args.shift if args.first.is_a?(Numeric)
|
4
4
|
factory = args.shift
|
5
|
-
|
5
|
+
factory_bot_args = args.shift || {}
|
6
6
|
|
7
7
|
if n
|
8
8
|
[].tap do |collection|
|
9
|
-
n.times.each { collection <<
|
9
|
+
n.times.each { collection << FactoryBot.build(factory.to_s.singularize.to_sym, factory_bot_args) }
|
10
10
|
end
|
11
11
|
else
|
12
|
-
|
12
|
+
FactoryBot.build(factory.to_s.singularize.to_sym, factory_bot_args)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
16
|
def Generate(*args) # rubocop:disable Style/MethodName
|
17
17
|
n = args.shift if args.first.is_a?(Numeric)
|
18
18
|
factory = args.shift
|
19
|
-
|
19
|
+
factory_bot_args = args.shift || {}
|
20
20
|
|
21
21
|
if n
|
22
22
|
[].tap do |collection|
|
23
|
-
n.times.each { collection <<
|
23
|
+
n.times.each { collection << FactoryBot.create(factory.to_s.singularize.to_sym, factory_bot_args) }
|
24
24
|
end
|
25
25
|
else
|
26
|
-
|
26
|
+
FactoryBot.create(factory.to_s.singularize.to_sym, factory_bot_args)
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
@@ -64,8 +64,8 @@ def should_support_mysql_import_functionality
|
|
64
64
|
let(:columns) { %w(id author_name title) }
|
65
65
|
|
66
66
|
setup do
|
67
|
-
topics << Topic.create!(title: "LDAP", author_name: "Big Bird")
|
68
|
-
topics << Topic.create!(title: "Rails Recipes", author_name: "Elmo")
|
67
|
+
topics << Topic.create!(title: "LDAP", author_name: "Big Bird", content: "Putting Directories to Work.")
|
68
|
+
topics << Topic.create!(title: "Rails Recipes", author_name: "Elmo", content: "A trusted collection of solutions.")
|
69
69
|
end
|
70
70
|
|
71
71
|
it "synchronizes passed in ActiveRecord model instances with the data just imported" do
|
@@ -82,5 +82,17 @@ def should_support_mysql_import_functionality
|
|
82
82
|
assert_equal "Chad Fowler", topics.last.author_name, "wrong author!"
|
83
83
|
end
|
84
84
|
end
|
85
|
+
|
86
|
+
if ENV['AR_VERSION'].to_f >= 5.1
|
87
|
+
context "with virtual columns" do
|
88
|
+
let(:books) { [Book.new(author_name: "foo", title: "bar")] }
|
89
|
+
|
90
|
+
it "ignores virtual columns and creates record" do
|
91
|
+
assert_difference "Book.count", +1 do
|
92
|
+
Book.import books
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
85
97
|
end
|
86
98
|
end
|
@@ -24,6 +24,36 @@ def should_support_postgresql_import_functionality
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
+
context "setting attributes and marking clean" do
|
28
|
+
let(:topic) { Build(:topics) }
|
29
|
+
|
30
|
+
setup { Topic.import([topic]) }
|
31
|
+
|
32
|
+
it "assigns ids" do
|
33
|
+
assert topic.id.present?
|
34
|
+
end
|
35
|
+
|
36
|
+
it "marks models as clean" do
|
37
|
+
assert !topic.changed?
|
38
|
+
end
|
39
|
+
|
40
|
+
if ENV['AR_VERSION'].to_f > 4.1
|
41
|
+
it "moves the dirty changes to previous_changes" do
|
42
|
+
assert topic.previous_changes.present?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it "marks models as persisted" do
|
47
|
+
assert !topic.new_record?
|
48
|
+
assert topic.persisted?
|
49
|
+
end
|
50
|
+
|
51
|
+
it "assigns timestamps" do
|
52
|
+
assert topic.created_at.present?
|
53
|
+
assert topic.updated_at.present?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
27
57
|
describe "with query cache enabled" do
|
28
58
|
setup do
|
29
59
|
unless ActiveRecord::Base.connection.query_cache_enabled
|
@@ -61,6 +91,96 @@ def should_support_postgresql_import_functionality
|
|
61
91
|
assert_equal [], Book.import(books, no_returning: true).ids
|
62
92
|
end
|
63
93
|
end
|
94
|
+
|
95
|
+
describe "returning" do
|
96
|
+
let(:books) { [Book.new(author_name: "King", title: "It")] }
|
97
|
+
let(:result) { Book.import(books, returning: %w(author_name title)) }
|
98
|
+
let(:book_id) do
|
99
|
+
if RUBY_PLATFORM == 'java' || ENV['AR_VERSION'].to_i >= 5.0
|
100
|
+
books.first.id
|
101
|
+
else
|
102
|
+
books.first.id.to_s
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it "creates records" do
|
107
|
+
assert_difference("Book.count", +1) { result }
|
108
|
+
end
|
109
|
+
|
110
|
+
it "returns ids" do
|
111
|
+
result
|
112
|
+
assert_equal [book_id], result.ids
|
113
|
+
end
|
114
|
+
|
115
|
+
it "returns specified columns" do
|
116
|
+
assert_equal [%w(King It)], result.results
|
117
|
+
end
|
118
|
+
|
119
|
+
context "when given an empty array" do
|
120
|
+
let(:result) { Book.import([], returning: %w(title)) }
|
121
|
+
|
122
|
+
setup { result }
|
123
|
+
|
124
|
+
it "returns empty arrays for ids and results" do
|
125
|
+
assert_equal [], result.ids
|
126
|
+
assert_equal [], result.results
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context "when a returning column is a serialized attribute" do
|
131
|
+
let(:vendor) { Vendor.new(hours: { monday: '8-5' }) }
|
132
|
+
let(:result) { Vendor.import([vendor], returning: %w(hours)) }
|
133
|
+
|
134
|
+
it "creates records" do
|
135
|
+
assert_difference("Vendor.count", +1) { result }
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context "when primary key and returning overlap" do
|
140
|
+
let(:result) { Book.import(books, returning: %w(id title)) }
|
141
|
+
|
142
|
+
setup { result }
|
143
|
+
|
144
|
+
it "returns ids" do
|
145
|
+
assert_equal [book_id], result.ids
|
146
|
+
end
|
147
|
+
|
148
|
+
it "returns specified columns" do
|
149
|
+
assert_equal [[book_id, 'It']], result.results
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context "setting model attributes" do
|
154
|
+
let(:code) { 'abc' }
|
155
|
+
let(:discount) { 0.10 }
|
156
|
+
let(:original_promotion) do
|
157
|
+
Promotion.new(code: code, discount: discount)
|
158
|
+
end
|
159
|
+
let(:updated_promotion) do
|
160
|
+
Promotion.new(code: code, description: 'ABC discount')
|
161
|
+
end
|
162
|
+
let(:returning_columns) { %w(discount) }
|
163
|
+
|
164
|
+
setup do
|
165
|
+
Promotion.import([original_promotion])
|
166
|
+
Promotion.import([updated_promotion],
|
167
|
+
on_duplicate_key_update: { conflict_target: %i(code), columns: %i(description) },
|
168
|
+
returning: returning_columns)
|
169
|
+
end
|
170
|
+
|
171
|
+
it "sets model attributes" do
|
172
|
+
assert_equal updated_promotion.discount, discount
|
173
|
+
end
|
174
|
+
|
175
|
+
context "returning multiple columns" do
|
176
|
+
let(:returning_columns) { %w(discount description) }
|
177
|
+
|
178
|
+
it "sets model attributes" do
|
179
|
+
assert_equal updated_promotion.discount, discount
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
64
184
|
end
|
65
185
|
|
66
186
|
if ENV['AR_VERSION'].to_f >= 4.0
|
@@ -134,6 +254,41 @@ def should_support_postgresql_import_functionality
|
|
134
254
|
assert_equal({}, Vendor.first.json_data)
|
135
255
|
end
|
136
256
|
end
|
257
|
+
|
258
|
+
%w(json jsonb).each do |json_type|
|
259
|
+
describe "with pure #{json_type} fields" do
|
260
|
+
let(:data) { { a: :b } }
|
261
|
+
let(:json_field_name) { "pure_#{json_type}_data" }
|
262
|
+
it "imports the values from saved records" do
|
263
|
+
vendor = Vendor.create!(name: 'Vendor 1', json_field_name => data)
|
264
|
+
|
265
|
+
Vendor.import [vendor], on_duplicate_key_update: [json_field_name]
|
266
|
+
assert_equal(data.as_json, vendor.reload[json_field_name])
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
describe "with enum field" do
|
273
|
+
let(:vendor_type) { "retailer" }
|
274
|
+
it "imports the correct values for enum fields" do
|
275
|
+
vendor = Vendor.new(name: 'Vendor 1', vendor_type: vendor_type)
|
276
|
+
assert_difference "Vendor.count", +1 do
|
277
|
+
Vendor.import [vendor]
|
278
|
+
end
|
279
|
+
assert_equal(vendor_type, Vendor.first.vendor_type)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
describe "with binary field" do
|
284
|
+
let(:binary_value) { "\xE0'c\xB2\xB0\xB3Bh\\\xC2M\xB1m\\I\xC4r".force_encoding('ASCII-8BIT') }
|
285
|
+
it "imports the correct values for binary fields" do
|
286
|
+
alarms = [Alarm.new(device_id: 1, alarm_type: 1, status: 1, secret_key: binary_value)]
|
287
|
+
assert_difference "Alarm.count", +1 do
|
288
|
+
Alarm.import alarms
|
289
|
+
end
|
290
|
+
assert_equal(binary_value, Alarm.first.secret_key)
|
291
|
+
end
|
137
292
|
end
|
138
293
|
end
|
139
294
|
|
@@ -176,7 +331,7 @@ def should_support_postgresql_upsert_functionality
|
|
176
331
|
# would be associated with the wrong parent.
|
177
332
|
it ":on_duplicate_key_ignore is ignored" do
|
178
333
|
assert_raise ActiveRecord::RecordNotUnique do
|
179
|
-
Topic.import mixed_topics, recursive: true, on_duplicate_key_ignore: true
|
334
|
+
Topic.import mixed_topics, recursive: true, on_duplicate_key_ignore: true, validate: false
|
180
335
|
end
|
181
336
|
end
|
182
337
|
end
|
@@ -190,6 +345,30 @@ def should_support_postgresql_upsert_functionality
|
|
190
345
|
end
|
191
346
|
|
192
347
|
context "using a hash" do
|
348
|
+
context "with :columns :all" do
|
349
|
+
let(:columns) { %w( id title author_name author_email_address parent_id ) }
|
350
|
+
let(:updated_values) { [[99, "Book - 2nd Edition", "Jane Doe", "janedoe@example.com", 57]] }
|
351
|
+
|
352
|
+
macro(:perform_import) do |*opts|
|
353
|
+
Topic.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: { conflict_target: :id, columns: :all }, validate: false)
|
354
|
+
end
|
355
|
+
|
356
|
+
setup do
|
357
|
+
values = [[99, "Book", "John Doe", "john@doe.com", 17, 3]]
|
358
|
+
Topic.import columns + ['replies_count'], values, validate: false
|
359
|
+
end
|
360
|
+
|
361
|
+
it "should update all specified columns" do
|
362
|
+
perform_import
|
363
|
+
updated_topic = Topic.find(99)
|
364
|
+
assert_equal 'Book - 2nd Edition', updated_topic.title
|
365
|
+
assert_equal 'Jane Doe', updated_topic.author_name
|
366
|
+
assert_equal 'janedoe@example.com', updated_topic.author_email_address
|
367
|
+
assert_equal 57, updated_topic.parent_id
|
368
|
+
assert_equal 3, updated_topic.replies_count
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
193
372
|
context "with :columns a hash" do
|
194
373
|
let(:columns) { %w( id title author_name author_email_address parent_id ) }
|
195
374
|
let(:values) { [[99, "Book", "John Doe", "john@doe.com", 17]] }
|
@@ -204,6 +383,13 @@ def should_support_postgresql_upsert_functionality
|
|
204
383
|
@topic = Topic.find 99
|
205
384
|
end
|
206
385
|
|
386
|
+
it "should not modify the passed in :on_duplicate_key_update columns array" do
|
387
|
+
assert_nothing_raised do
|
388
|
+
columns = %w(title author_name).freeze
|
389
|
+
Topic.import columns, [%w(foo, bar)], { on_duplicate_key_update: { columns: columns }.freeze }.freeze
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
207
393
|
context "using string hash map" do
|
208
394
|
let(:update_columns) { { "title" => "title", "author_email_address" => "author_email_address", "parent_id" => "parent_id" } }
|
209
395
|
should_support_on_duplicate_key_update
|
@@ -273,6 +459,39 @@ def should_support_postgresql_upsert_functionality
|
|
273
459
|
end
|
274
460
|
end
|
275
461
|
|
462
|
+
context 'with :condition' do
|
463
|
+
let(:columns) { %w( id device_id alarm_type status metadata) }
|
464
|
+
let(:values) { [[99, 17, 1, 1, 'foo']] }
|
465
|
+
let(:updated_values) { [[99, 17, 1, 1, 'bar']] }
|
466
|
+
|
467
|
+
macro(:perform_import) do |*opts|
|
468
|
+
Alarm.import(
|
469
|
+
columns,
|
470
|
+
updated_values,
|
471
|
+
opts.extract_options!.merge(
|
472
|
+
on_duplicate_key_update: {
|
473
|
+
conflict_target: [:id],
|
474
|
+
condition: "alarms.metadata NOT LIKE '%foo%'",
|
475
|
+
columns: [:metadata]
|
476
|
+
},
|
477
|
+
validate: false
|
478
|
+
)
|
479
|
+
)
|
480
|
+
end
|
481
|
+
|
482
|
+
macro(:updated_alarm) { Alarm.find(@alarm.id) }
|
483
|
+
|
484
|
+
setup do
|
485
|
+
Alarm.import columns, values, validate: false
|
486
|
+
@alarm = Alarm.find 99
|
487
|
+
end
|
488
|
+
|
489
|
+
it 'should not update fields not matched' do
|
490
|
+
perform_import
|
491
|
+
assert_equal 'foo', updated_alarm.metadata
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
276
495
|
context "with :constraint_name" do
|
277
496
|
let(:columns) { %w( id title author_name author_email_address parent_id ) }
|
278
497
|
let(:values) { [[100, "Book", "John Doe", "john@doe.com", 17]] }
|