activerecord-import 1.4.1 → 1.8.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yaml +53 -13
  3. data/.gitignore +4 -0
  4. data/.rubocop.yml +7 -4
  5. data/.rubocop_todo.yml +10 -16
  6. data/CHANGELOG.md +48 -1
  7. data/Dockerfile +23 -0
  8. data/Gemfile +15 -7
  9. data/README.markdown +44 -5
  10. data/Rakefile +1 -0
  11. data/activerecord-import.gemspec +4 -0
  12. data/benchmarks/benchmark.rb +3 -3
  13. data/benchmarks/lib/base.rb +2 -2
  14. data/benchmarks/lib/cli_parser.rb +2 -2
  15. data/docker-compose.yml +34 -0
  16. data/gemfiles/7.1.gemfile +3 -0
  17. data/gemfiles/7.2.gemfile +3 -0
  18. data/lib/activerecord-import/active_record/adapters/trilogy_adapter.rb +8 -0
  19. data/lib/activerecord-import/adapters/abstract_adapter.rb +6 -5
  20. data/lib/activerecord-import/adapters/mysql_adapter.rb +24 -18
  21. data/lib/activerecord-import/adapters/postgresql_adapter.rb +26 -18
  22. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +29 -23
  23. data/lib/activerecord-import/adapters/trilogy_adapter.rb +7 -0
  24. data/lib/activerecord-import/import.rb +63 -28
  25. data/lib/activerecord-import/value_sets_parser.rb +1 -0
  26. data/lib/activerecord-import/version.rb +1 -1
  27. data/lib/activerecord-import.rb +0 -1
  28. data/test/adapters/trilogy.rb +9 -0
  29. data/test/database.yml.sample +7 -0
  30. data/test/github/database.yml +4 -0
  31. data/test/jdbcmysql/import_test.rb +3 -3
  32. data/test/jdbcpostgresql/import_test.rb +2 -2
  33. data/test/jdbcsqlite3/import_test.rb +2 -2
  34. data/test/makara_postgis/import_test.rb +2 -2
  35. data/test/models/author.rb +7 -0
  36. data/test/models/bike_maker.rb +1 -0
  37. data/test/models/book.rb +5 -2
  38. data/test/models/composite_book.rb +19 -0
  39. data/test/models/composite_chapter.rb +9 -0
  40. data/test/models/customer.rb +14 -4
  41. data/test/models/order.rb +13 -4
  42. data/test/models/tag.rb +6 -1
  43. data/test/models/tag_alias.rb +7 -1
  44. data/test/models/topic.rb +5 -0
  45. data/test/models/widget.rb +10 -3
  46. data/test/mysql2/import_test.rb +3 -3
  47. data/test/mysql2_makara/import_test.rb +3 -3
  48. data/test/mysqlspatial2/import_test.rb +3 -3
  49. data/test/postgis/import_test.rb +2 -2
  50. data/test/postgresql/import_test.rb +2 -2
  51. data/test/schema/generic_schema.rb +4 -1
  52. data/test/schema/jdbcpostgresql_schema.rb +1 -1
  53. data/test/schema/postgis_schema.rb +1 -1
  54. data/test/schema/postgresql_schema.rb +35 -4
  55. data/test/sqlite3/import_test.rb +2 -2
  56. data/test/support/postgresql/import_examples.rb +12 -0
  57. data/test/support/shared_examples/on_duplicate_key_update.rb +67 -10
  58. data/test/support/shared_examples/recursive_import.rb +67 -1
  59. data/test/test_helper.rb +6 -4
  60. data/test/trilogy/import_test.rb +7 -0
  61. data/test/value_sets_bytes_parser_test.rb +1 -1
  62. data/test/value_sets_records_parser_test.rb +1 -1
  63. metadata +24 -7
@@ -4,6 +4,7 @@ module Bike
4
4
  def self.table_name_prefix
5
5
  'bike_'
6
6
  end
7
+
7
8
  class Maker < ActiveRecord::Base
8
9
  end
9
10
  end
data/test/models/book.rb CHANGED
@@ -2,8 +2,11 @@
2
2
 
3
3
  class Book < ActiveRecord::Base
4
4
  belongs_to :topic, inverse_of: :books
5
- belongs_to :tag, foreign_key: [:tag_id, :parent_id]
6
-
5
+ if ENV['AR_VERSION'].to_f <= 7.0
6
+ belongs_to :tag, foreign_key: [:tag_id, :parent_id] unless ENV["SKIP_COMPOSITE_PK"]
7
+ else
8
+ belongs_to :tag, query_constraints: [:tag_id, :parent_id] unless ENV["SKIP_COMPOSITE_PK"]
9
+ end
7
10
  has_many :chapters, inverse_of: :book
8
11
  has_many :discounts, as: :discountable
9
12
  has_many :end_notes, inverse_of: :book
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CompositeBook < ActiveRecord::Base
4
+ self.primary_key = %i[id author_id]
5
+ belongs_to :author
6
+ if ENV['AR_VERSION'].to_f <= 7.0
7
+ unless ENV["SKIP_COMPOSITE_PK"]
8
+ has_many :composite_chapters, inverse_of: :composite_book,
9
+ foreign_key: [:id, :author_id]
10
+ end
11
+ else
12
+ has_many :composite_chapters, inverse_of: :composite_book,
13
+ query_constraints: [:id, :author_id]
14
+ end
15
+
16
+ def self.sequence_name
17
+ "composite_book_id_seq"
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CompositeChapter < ActiveRecord::Base
4
+ if ENV['AR_VERSION'].to_f >= 7.1
5
+ belongs_to :composite_book, inverse_of: :composite_chapters,
6
+ query_constraints: [:composite_book_id, :author_id]
7
+ end
8
+ validates :title, presence: true
9
+ end
@@ -1,8 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Customer < ActiveRecord::Base
4
- has_many :orders,
5
- inverse_of: :customer,
6
- primary_key: %i(account_id id),
7
- foreign_key: %i(account_id customer_id)
4
+ unless ENV["SKIP_COMPOSITE_PK"]
5
+ if ENV['AR_VERSION'].to_f <= 7.0
6
+ has_many :orders,
7
+ inverse_of: :customer,
8
+ primary_key: %i(account_id id),
9
+ foreign_key: %i(account_id customer_id)
10
+ else
11
+ has_many :orders,
12
+ inverse_of: :customer,
13
+ primary_key: %i(account_id id),
14
+ query_constraints: %i(account_id customer_id)
15
+ end
16
+
17
+ end
8
18
  end
data/test/models/order.rb CHANGED
@@ -1,8 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Order < ActiveRecord::Base
4
- belongs_to :customer,
5
- inverse_of: :orders,
6
- primary_key: %i(account_id id),
7
- foreign_key: %i(account_id customer_id)
4
+ unless ENV["SKIP_COMPOSITE_PK"]
5
+ if ENV['AR_VERSION'].to_f <= 7.0
6
+ belongs_to :customer,
7
+ inverse_of: :orders,
8
+ primary_key: %i(account_id id),
9
+ foreign_key: %i(account_id customer_id)
10
+ else
11
+ belongs_to :customer,
12
+ inverse_of: :orders,
13
+ primary_key: %i(account_id id),
14
+ query_constraints: %i(account_id customer_id)
15
+ end
16
+ end
8
17
  end
data/test/models/tag.rb CHANGED
@@ -1,7 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Tag < ActiveRecord::Base
4
- self.primary_keys = :tag_id, :publisher_id unless ENV["SKIP_COMPOSITE_PK"]
4
+ if ENV['AR_VERSION'].to_f <= 7.0
5
+ self.primary_keys = :tag_id, :publisher_id unless ENV["SKIP_COMPOSITE_PK"]
6
+ else
7
+ self.primary_key = [:tag_id, :publisher_id] unless ENV["SKIP_COMPOSITE_PK"]
8
+ end
9
+ self.primary_key = [:tag_id, :publisher_id] unless ENV["SKIP_COMPOSITE_PK"]
5
10
  has_many :books, inverse_of: :tag
6
11
  has_many :tag_aliases, inverse_of: :tag
7
12
  end
@@ -1,5 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class TagAlias < ActiveRecord::Base
4
- belongs_to :tag, foreign_key: [:tag_id, :parent_id], required: true
4
+ unless ENV["SKIP_COMPOSITE_PK"]
5
+ if ENV['AR_VERSION'].to_f <= 7.0
6
+ belongs_to :tag, foreign_key: [:tag_id, :parent_id], required: true
7
+ else
8
+ belongs_to :tag, query_constraints: [:tag_id, :parent_id], required: true
9
+ end
10
+ end
5
11
  end
data/test/models/topic.rb CHANGED
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Topic < ActiveRecord::Base
4
+ if ENV['AR_VERSION'].to_f >= 6.0
5
+ self.ignored_columns = [:priority]
6
+ end
7
+ alias_attribute :name, :title
8
+
4
9
  validates_presence_of :author_name
5
10
  validates :title, numericality: { only_integer: true }, on: :context_test
6
11
  validates :title, uniqueness: true
@@ -19,8 +19,15 @@ class Widget < ActiveRecord::Base
19
19
 
20
20
  default_scope -> { where(active: true) }
21
21
 
22
- serialize :data, Hash
23
- serialize :json_data, JSON
22
+ if ENV['AR_VERSION'].to_f >= 7.1
23
+ serialize :data, coder: YAML
24
+ serialize :json_data, coder: JSON
25
+ serialize :custom_data, coder: CustomCoder.new
26
+ else
27
+ serialize :data, Hash
28
+ serialize :json_data, JSON
29
+ serialize :custom_data, CustomCoder.new
30
+ end
31
+
24
32
  serialize :unspecified_data
25
- serialize :custom_data, CustomCoder.new
26
33
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
4
- require File.expand_path(File.dirname(__FILE__) + '/../support/assertions')
5
- require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
4
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/assertions")
5
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/mysql/import_examples")
6
6
 
7
7
  should_support_mysql_import_functionality
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
4
4
 
5
- require File.expand_path(File.dirname(__FILE__) + '/../support/assertions')
6
- require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
5
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/assertions")
6
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/mysql/import_examples")
7
7
 
8
8
  should_support_mysql_import_functionality
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
4
4
 
5
- require File.expand_path(File.dirname(__FILE__) + '/../support/assertions')
6
- require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
5
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/assertions")
6
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/mysql/import_examples")
7
7
 
8
8
  should_support_mysql_import_functionality
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
4
- require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
4
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/postgresql/import_examples")
5
5
 
6
6
  should_support_postgresql_import_functionality
7
7
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
4
- require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
4
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/postgresql/import_examples")
5
5
 
6
6
  should_support_postgresql_import_functionality
@@ -2,8 +2,10 @@
2
2
 
3
3
  ActiveRecord::Schema.define do
4
4
  create_table :schema_info, force: :cascade do |t|
5
- t.integer :version, unique: true
5
+ t.integer :version
6
6
  end
7
+ add_index :schema_info, :version, unique: true
8
+
7
9
  SchemaInfo.create version: SchemaInfo::VERSION
8
10
 
9
11
  create_table :group, force: :cascade do |t|
@@ -22,6 +24,7 @@ ActiveRecord::Schema.define do
22
24
  t.boolean :approved, default: '1'
23
25
  t.integer :replies_count
24
26
  t.integer :parent_id
27
+ t.integer :priority, default: 0
25
28
  t.string :type
26
29
  t.datetime :created_at
27
30
  t.datetime :created_on
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require File.expand_path(File.dirname(__FILE__) + '/postgresql_schema')
3
+ require File.expand_path("#{File.dirname(__FILE__)}/postgresql_schema")
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require File.expand_path(File.dirname(__FILE__) + '/postgresql_schema')
3
+ require File.expand_path("#{File.dirname(__FILE__)}/postgresql_schema")
@@ -8,10 +8,7 @@ ActiveRecord::Schema.define do
8
8
  # create ENUM if it does not exist yet
9
9
  begin
10
10
  execute('CREATE TYPE vendor_type AS ENUM (\'wholesaler\', \'retailer\');')
11
- rescue ActiveRecord::StatementInvalid => e
12
- # since PostgreSQL does not support IF NOT EXISTS when creating a TYPE,
13
- # rescue the error and check the error class
14
- raise unless e.cause.is_a? PG::DuplicateObject
11
+ rescue ActiveRecord::StatementInvalid
15
12
  execute('ALTER TYPE vendor_type ADD VALUE IF NOT EXISTS \'wholesaler\';')
16
13
  execute('ALTER TYPE vendor_type ADD VALUE IF NOT EXISTS \'retailer\';')
17
14
  end
@@ -60,4 +57,38 @@ ActiveRecord::Schema.define do
60
57
  end
61
58
 
62
59
  add_index :alarms, [:device_id, :alarm_type], unique: true, where: 'status <> 0'
60
+
61
+ unless ENV["SKIP_COMPOSITE_PK"]
62
+ create_table :authors, force: :cascade do |t|
63
+ t.string :name
64
+ end
65
+
66
+ execute %(
67
+ DROP SEQUENCE IF EXISTS composite_book_id_seq CASCADE;
68
+ CREATE SEQUENCE composite_book_id_seq
69
+ AS integer
70
+ START WITH 1
71
+ INCREMENT BY 1
72
+ NO MINVALUE
73
+ NO MAXVALUE
74
+ CACHE 1;
75
+
76
+ DROP TABLE IF EXISTS composite_books;
77
+ CREATE TABLE composite_books (
78
+ id bigint DEFAULT nextval('composite_book_id_seq'::regclass) NOT NULL,
79
+ title character varying,
80
+ author_id bigint
81
+ );
82
+
83
+ ALTER TABLE ONLY composite_books ADD CONSTRAINT fk_rails_040a418131 FOREIGN KEY (author_id) REFERENCES authors(id);
84
+ ).split.join(' ').strip
85
+ end
86
+
87
+ create_table :composite_chapters, force: :cascade do |t|
88
+ t.string :title
89
+ t.integer :composite_book_id, null: false
90
+ t.integer :author_id, null: false
91
+ t.datetime :created_at
92
+ t.datetime :updated_at
93
+ end
63
94
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
4
- require File.expand_path(File.dirname(__FILE__) + '/../support/sqlite3/import_examples')
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
4
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/sqlite3/import_examples")
5
5
 
6
6
  should_support_sqlite3_import_functionality
@@ -351,6 +351,18 @@ def should_support_postgresql_import_functionality
351
351
  assert_equal db_customer.orders.last, db_order
352
352
  assert_not_equal db_order.customer_id, nil
353
353
  end
354
+
355
+ it "should import models with auto-incrementing ID successfully" do
356
+ author = Author.create!(name: "Foo Barson")
357
+
358
+ books = []
359
+ 2.times do |i|
360
+ books << CompositeBook.new(author_id: author.id, title: "book #{i}")
361
+ end
362
+ assert_difference "CompositeBook.count", +2 do
363
+ CompositeBook.import books
364
+ end
365
+ end
354
366
  end
355
367
  end
356
368
  end
@@ -26,7 +26,7 @@ def should_support_basic_on_duplicate_key_update
26
26
  User.import(updated_users, on_duplicate_key_update: [:name])
27
27
  assert User.count == updated_users.length
28
28
  User.all.each_with_index do |user, i|
29
- assert_equal user.name, users[i].name + ' Rothschild'
29
+ assert_equal user.name, "#{users[i].name} Rothschild"
30
30
  assert_equal 1, user.lock_version
31
31
  end
32
32
  end
@@ -50,7 +50,7 @@ def should_support_basic_on_duplicate_key_update
50
50
  User.import(columns, updated_values, on_duplicate_key_update: [:name])
51
51
  assert User.count == updated_values.length
52
52
  User.all.each_with_index do |user, i|
53
- assert_equal user.name, users[i].name + ' Rothschild'
53
+ assert_equal user.name, "#{users[i].name} Rothschild"
54
54
  assert_equal 1, user.lock_version
55
55
  end
56
56
  end
@@ -72,7 +72,7 @@ def should_support_basic_on_duplicate_key_update
72
72
  User.import(updated_values, on_duplicate_key_update: [:name])
73
73
  assert User.count == updated_values.length
74
74
  User.all.each_with_index do |user, i|
75
- assert_equal user.name, users[i].name + ' Rothschild'
75
+ assert_equal user.name, "#{users[i].name} Rothschild"
76
76
  assert_equal 1, user.lock_version
77
77
  end
78
78
  updated_values2 = User.all.map do |user|
@@ -82,7 +82,7 @@ def should_support_basic_on_duplicate_key_update
82
82
  User.import(updated_values2, on_duplicate_key_update: [:name])
83
83
  assert User.count == updated_values2.length
84
84
  User.all.each_with_index do |user, i|
85
- assert_equal user.name, users[i].name + ' Rothschild jr.'
85
+ assert_equal user.name, "#{users[i].name} Rothschild jr."
86
86
  assert_equal 2, user.lock_version
87
87
  end
88
88
  end
@@ -104,7 +104,7 @@ def should_support_basic_on_duplicate_key_update
104
104
  Account.import(updated_accounts, on_duplicate_key_update: [:id, :name])
105
105
  assert Account.count == updated_accounts.length
106
106
  Account.all.each_with_index do |user, i|
107
- assert_equal user.name, accounts[i].name + ' Rothschild'
107
+ assert_equal user.name, "#{accounts[i].name} Rothschild"
108
108
  assert_equal 1, user.lock
109
109
  end
110
110
  end
@@ -128,7 +128,7 @@ def should_support_basic_on_duplicate_key_update
128
128
  Account.import(columns, updated_values, on_duplicate_key_update: [:name])
129
129
  assert Account.count == updated_values.length
130
130
  Account.all.each_with_index do |user, i|
131
- assert_equal user.name, accounts[i].name + ' Rothschild'
131
+ assert_equal user.name, "#{accounts[i].name} Rothschild"
132
132
  assert_equal 1, user.lock
133
133
  end
134
134
  end
@@ -150,7 +150,7 @@ def should_support_basic_on_duplicate_key_update
150
150
  Account.import(updated_values, on_duplicate_key_update: [:name])
151
151
  assert Account.count == updated_values.length
152
152
  Account.all.each_with_index do |user, i|
153
- assert_equal user.name, accounts[i].name + ' Rothschild'
153
+ assert_equal user.name, "#{accounts[i].name} Rothschild"
154
154
  assert_equal 1, user.lock
155
155
  end
156
156
  end
@@ -172,10 +172,11 @@ def should_support_basic_on_duplicate_key_update
172
172
  Bike::Maker.import(updated_makers, on_duplicate_key_update: [:name])
173
173
  assert Bike::Maker.count == updated_makers.length
174
174
  Bike::Maker.all.each_with_index do |maker, i|
175
- assert_equal maker.name, makers[i].name + ' bikes'
175
+ assert_equal maker.name, "#{makers[i].name} bikes"
176
176
  assert_equal 1, maker.lock_version
177
177
  end
178
178
  end
179
+
179
180
  it 'update the lock_version of models separated by namespaces by array' do
180
181
  makers = [
181
182
  Bike::Maker.new(name: 'Yamaha'),
@@ -195,7 +196,7 @@ def should_support_basic_on_duplicate_key_update
195
196
  Bike::Maker.import(columns, updated_values, on_duplicate_key_update: [:name])
196
197
  assert Bike::Maker.count == updated_values.length
197
198
  Bike::Maker.all.each_with_index do |maker, i|
198
- assert_equal maker.name, makers[i].name + ' bikes'
199
+ assert_equal maker.name, "#{makers[i].name} bikes"
199
200
  assert_equal 1, maker.lock_version
200
201
  end
201
202
  end
@@ -217,11 +218,39 @@ def should_support_basic_on_duplicate_key_update
217
218
  Bike::Maker.import(updated_values, on_duplicate_key_update: [:name])
218
219
  assert Bike::Maker.count == updated_values.length
219
220
  Bike::Maker.all.each_with_index do |maker, i|
220
- assert_equal maker.name, makers[i].name + ' bikes'
221
+ assert_equal maker.name, "#{makers[i].name} bikes"
221
222
  assert_equal 1, maker.lock_version
222
223
  end
223
224
  end
224
225
  end
226
+
227
+ context 'with locking disabled' do
228
+ it 'does not update the lock_version' do
229
+ users = [
230
+ User.new(name: 'Salomon'),
231
+ User.new(name: 'Nathan')
232
+ ]
233
+ User.import(users)
234
+ assert User.count == users.length
235
+ User.all.each do |user|
236
+ assert_equal 0, user.lock_version
237
+ end
238
+ updated_users = User.all.map do |user|
239
+ user.name += ' Rothschild'
240
+ user
241
+ end
242
+
243
+ ActiveRecord::Base.lock_optimistically = false # Disable locking
244
+ User.import(updated_users, on_duplicate_key_update: [:name])
245
+ ActiveRecord::Base.lock_optimistically = true # Enable locking
246
+
247
+ assert User.count == updated_users.length
248
+ User.all.each_with_index do |user, i|
249
+ assert_equal user.name, "#{users[i].name} Rothschild"
250
+ assert_equal 0, user.lock_version
251
+ end
252
+ end
253
+ end
225
254
  end
226
255
 
227
256
  context "with :on_duplicate_key_update" do
@@ -316,6 +345,34 @@ def should_support_basic_on_duplicate_key_update
316
345
  should_support_on_duplicate_key_update
317
346
  should_update_fields_mentioned
318
347
  end
348
+
349
+ context "using column aliases" do
350
+ let(:columns) { %w( id title author_name author_email_address parent_id ) }
351
+ let(:update_columns) { %w(title author_email_address parent_id) }
352
+
353
+ context "with column aliases in column list" do
354
+ let(:columns) { %w( id name author_name author_email_address parent_id ) }
355
+ should_support_on_duplicate_key_update
356
+ should_update_fields_mentioned
357
+ end
358
+
359
+ context "with column aliases in update columns list" do
360
+ let(:update_columns) { %w(name author_email_address parent_id) }
361
+ should_support_on_duplicate_key_update
362
+ should_update_fields_mentioned
363
+ end
364
+ end
365
+
366
+ if ENV['AR_VERSION'].to_i >= 6.0
367
+ context "using ignored columns" do
368
+ let(:columns) { %w( id title author_name author_email_address parent_id priority ) }
369
+ let(:values) { [[99, "Book", "John Doe", "john@doe.com", 17, 1]] }
370
+ let(:update_columns) { %w(name author_email_address parent_id priority) }
371
+ let(:updated_values) { [[99, "Book - 2nd Edition", "Author Should Not Change", "johndoe@example.com", 57, 2]] }
372
+ should_support_on_duplicate_key_update
373
+ should_update_fields_mentioned
374
+ end
375
+ end
319
376
  end
320
377
 
321
378
  context "with a table that has a non-standard primary key" do
@@ -147,7 +147,7 @@ def should_support_recursive_import
147
147
  end
148
148
 
149
149
  books.each do |book|
150
- assert_equal book.topic_id, nil
150
+ assert_nil book.topic_id, nil
151
151
  end
152
152
  end
153
153
 
@@ -165,6 +165,24 @@ def should_support_recursive_import
165
165
  assert_equal 1, tags[0].tag_id
166
166
  assert_equal 2, tags[1].tag_id
167
167
  end
168
+
169
+ if ENV['AR_VERSION'].to_f >= 7.1
170
+ it "should import models with auto-incrementing ID successfully with recursive set to true" do
171
+ author = Author.create!(name: "Foo Barson")
172
+ books = []
173
+ 2.times do |i|
174
+ books << CompositeBook.new(author_id: author.id, title: "Book #{i}", composite_chapters: [
175
+ CompositeChapter.new(title: "Book #{i} composite chapter 1"),
176
+ CompositeChapter.new(title: "Book #{i} composite chapter 2"),
177
+ ])
178
+ end
179
+ assert_difference "CompositeBook.count", +2 do
180
+ assert_difference "CompositeChapter.count", +4 do
181
+ CompositeBook.import books, recursive: true
182
+ end
183
+ end
184
+ end
185
+ end
168
186
  end
169
187
  end
170
188
 
@@ -213,6 +231,54 @@ def should_support_recursive_import
213
231
  end
214
232
  end
215
233
  end
234
+
235
+ describe "recursive_on_duplicate_key_update" do
236
+ let(:new_topics) { Build(1, :topic_with_book) }
237
+
238
+ setup do
239
+ Topic.import new_topics, recursive: true
240
+ end
241
+
242
+ it "updates associated objects" do
243
+ new_author_name = 'Richard Bachman'
244
+ topic = new_topics.first
245
+ topic.books.each do |book|
246
+ book.author_name = new_author_name
247
+ end
248
+
249
+ assert_nothing_raised do
250
+ Topic.import new_topics,
251
+ recursive: true,
252
+ on_duplicate_key_update: [:id],
253
+ recursive_on_duplicate_key_update: {
254
+ books: { conflict_target: [:id], columns: [:author_name] }
255
+ }
256
+ end
257
+ Topic.find(topic.id).books.each do |book|
258
+ assert_equal new_author_name, book.author_name
259
+ end
260
+ end
261
+
262
+ it "updates nested associated objects" do
263
+ new_chapter_title = 'The Final Chapter'
264
+ book = new_topics.first.books.first
265
+ book.author_name = 'Richard Bachman'
266
+
267
+ example_chapter = book.chapters.first
268
+ example_chapter.title = new_chapter_title
269
+
270
+ assert_nothing_raised do
271
+ Topic.import new_topics,
272
+ recursive: true,
273
+ on_duplicate_key_update: [:id],
274
+ recursive_on_duplicate_key_update: {
275
+ books: { conflict_target: [:id], columns: [:author_name] },
276
+ chapters: { conflict_target: [:id], columns: [:title] }
277
+ }
278
+ end
279
+ assert_equal new_chapter_title, Chapter.find(example_chapter.id).title
280
+ end
281
+ end
216
282
  end
217
283
 
218
284
  # If returning option is provided, it is only applied to top level models so that SQL with invalid
data/test/test_helper.rb CHANGED
@@ -24,7 +24,7 @@ if ActiveSupport::VERSION::STRING < "4.0"
24
24
  require 'mocha/test_unit'
25
25
  else
26
26
  require 'active_support/testing/autorun'
27
- require "mocha/mini_test"
27
+ require "mocha/minitest"
28
28
  end
29
29
 
30
30
  require 'timecop'
@@ -33,7 +33,9 @@ require 'chronic'
33
33
  begin
34
34
  require 'composite_primary_keys'
35
35
  rescue LoadError
36
- ENV["SKIP_COMPOSITE_PK"] = "true"
36
+ if ENV['AR_VERSION'].to_f <= 7.1
37
+ ENV['SKIP_COMPOSITE_PK'] = 'true'
38
+ end
37
39
  end
38
40
 
39
41
  # Support MySQL 5.7
@@ -84,7 +86,7 @@ ActiveSupport::Notifications.subscribe(/active_record.sql/) do |_, _, _, _, hsh|
84
86
  end
85
87
 
86
88
  require "factory_bot"
87
- Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each { |file| require file }
89
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].sort.each { |file| require file }
88
90
 
89
91
  # Load base/generic schema
90
92
  require test_dir.join("schema/version")
@@ -92,7 +94,7 @@ require test_dir.join("schema/generic_schema")
92
94
  adapter_schema = test_dir.join("schema/#{adapter}_schema.rb")
93
95
  require adapter_schema if File.exist?(adapter_schema)
94
96
 
95
- Dir[File.dirname(__FILE__) + "/models/*.rb"].each { |file| require file }
97
+ Dir["#{File.dirname(__FILE__)}/models/*.rb"].sort.each { |file| require file }
96
98
 
97
99
  # Prevent this deprecation warning from breaking the tests.
98
100
  Rake::FileList.send(:remove_method, :import)
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
4
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/assertions")
5
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/mysql/import_examples")
6
+
7
+ should_support_mysql_import_functionality
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require File.expand_path(File.dirname(__FILE__) + '/test_helper')
3
+ require File.expand_path("#{File.dirname(__FILE__)}/test_helper")
4
4
 
5
5
  require 'activerecord-import/value_sets_parser'
6
6
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require File.expand_path(File.dirname(__FILE__) + '/test_helper')
3
+ require File.expand_path("#{File.dirname(__FILE__)}/test_helper")
4
4
 
5
5
  require 'activerecord-import/value_sets_parser'
6
6