activerecord-import 1.5.1 → 1.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8b122b955b4a926db451a6a3ef72405ade6d69b54876bccb9ea51680486b328
4
- data.tar.gz: 3af5c65f25ffb54d338d012aab4f3608513071287c8a759f3ba678850a81f6e0
3
+ metadata.gz: 5aeed63fcbc9ad647ab901931265d2765baf5d0e5a1e9d2cf2c68af32dd5e1be
4
+ data.tar.gz: 47480cc7244aada4bc69ba28043e8ab85ae1cece4420db4453400425a5773126
5
5
  SHA512:
6
- metadata.gz: 638f6a8062713b07b1c2ebe866fa647c254d42e594c0fa056b5a710bcd9a2560b2217db73a89f5804c9fc1a71d5de05fc98625771e6225e9e148f58048618079
7
- data.tar.gz: 806d66d95034276b0daec9c6c207b8a6187ec0950a44b312267aeef4d491b1c9f5e036641df35950296180f124315fc220e9af5d5b4d4057278176dcf667b1c3
6
+ metadata.gz: 5f6b5749dd6ce81784f7016c6227a6d0f5047c59a45beb13616853a89599ac507241012a92c3e4f9996c7a465d4d926e0e8d1183af1854f7a0c8a05e2e59219f
7
+ data.tar.gz: 72b00e7a5c61af3923b618ebe0d96f945a7af145f38ec5312962220b925e1dfc918da6028bb48d8ff7842c32584b34b06cec8fd8288dbb83212ca035f7a16c08
@@ -16,11 +16,25 @@ jobs:
16
16
  --health-interval 10s
17
17
  --health-timeout 5s
18
18
  --health-retries 5
19
+ mysql:
20
+ image: mysql:5.7
21
+ ports:
22
+ - 3306:3306
23
+ env:
24
+ MYSQL_ROOT_PASSWORD: root
25
+ MYSQL_USER: github
26
+ MYSQL_PASSWORD: github
27
+ MYSQL_DATABASE: activerecord_import_test
28
+ options: >-
29
+ --health-cmd "mysqladmin ping -h localhost"
30
+ --health-interval 10s
31
+ --health-timeout 5s
32
+ --health-retries 5
19
33
  strategy:
20
34
  fail-fast: false
21
35
  matrix:
22
36
  ruby:
23
- - 3.2
37
+ - 3.3
24
38
  env:
25
39
  - AR_VERSION: '7.1'
26
40
  RUBYOPT: --enable-frozen-string-literal
@@ -29,6 +43,15 @@ jobs:
29
43
  - AR_VERSION: 6.1
30
44
  RUBYOPT: --enable-frozen-string-literal
31
45
  include:
46
+ - ruby: 3.2
47
+ env:
48
+ AR_VERSION: '7.1'
49
+ - ruby: 3.2
50
+ env:
51
+ AR_VERSION: '7.0'
52
+ - ruby: 3.2
53
+ env:
54
+ AR_VERSION: 6.1
32
55
  - ruby: 3.1
33
56
  env:
34
57
  AR_VERSION: '7.1'
@@ -44,7 +67,7 @@ jobs:
44
67
  - ruby: '3.0'
45
68
  env:
46
69
  AR_VERSION: 6.1
47
- - ruby: jruby
70
+ - ruby: jruby-9.4.5.0
48
71
  env:
49
72
  AR_VERSION: '7.0'
50
73
  - ruby: 2.7
@@ -81,6 +104,7 @@ jobs:
81
104
  with:
82
105
  ruby-version: ${{ matrix.ruby }}
83
106
  bundler-cache: true
107
+ rubygems: latest
84
108
  - name: Set up databases
85
109
  run: |
86
110
  sudo /etc/init.d/mysql start
@@ -110,6 +134,9 @@ jobs:
110
134
  run: |
111
135
  bundle exec rake test:spatialite
112
136
  bundle exec rake test:sqlite3
137
+ - name: Run trilogy tests
138
+ if: ${{ matrix.env.AR_VERSION >= '7.0' && !startsWith(matrix.ruby, 'jruby') }}
139
+ run: bundle exec rake test:trilogy
113
140
  lint:
114
141
  runs-on: ubuntu-latest
115
142
  env:
data/.gitignore CHANGED
@@ -13,12 +13,16 @@ tmtags
13
13
  ## VIM
14
14
  *.swp
15
15
 
16
+ ## Idea
17
+ .idea
18
+
16
19
  ## PROJECT::GENERAL
17
20
  coverage
18
21
  rdoc
19
22
  pkg
20
23
  *.gem
21
24
  *.lock
25
+ .byebug_history
22
26
 
23
27
  ## PROJECT::SPECIFIC
24
28
  log/*.log
data/.rubocop_todo.yml CHANGED
@@ -24,3 +24,7 @@ Lint/UnusedMethodArgument:
24
24
  Style/CombinableLoops:
25
25
  Exclude:
26
26
  - 'test/support/shared_examples/recursive_import.rb'
27
+
28
+ Naming/FileName:
29
+ Exclude:
30
+ - 'lib/activerecord-import.rb'
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## Changes in 1.7.0
2
+
3
+ ### New Features
4
+
5
+ * Add support for ActiveRecord 7.1 composite primary keys. Thanks to @fragkakis via \##837.
6
+ * Add support for upserting associations when doing recursive imports. Thanks to @ramblex via \##778.
7
+
8
+ ## Changes in 1.6.0
9
+
10
+ ### New Features
11
+
12
+ * Add trilogy adapter support. Thanks to @zmariscal via \##825.
13
+
14
+ ### Fixes
15
+
16
+ * Use the locking_enabled? method provided by activerecord to decide whether the lock field should be updated. Thanks to @dombesz via \##822.
17
+
1
18
  ## Changes in 1.5.1
2
19
 
3
20
  ### Fixes
data/Gemfile CHANGED
@@ -26,6 +26,10 @@ platforms :ruby do
26
26
  gem "sqlite3", "~> #{sqlite3_version}"
27
27
  # seamless_database_pool requires Ruby ~> 2.0
28
28
  gem "seamless_database_pool", "~> 1.0.20" if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.0.0')
29
+ gem "trilogy" if version >= 6.0
30
+ if version >= 6.0 && version <= 7.0
31
+ gem "activerecord-trilogy-adapter"
32
+ end
29
33
  end
30
34
 
31
35
  platforms :jruby do
@@ -37,7 +41,11 @@ platforms :jruby do
37
41
  end
38
42
 
39
43
  # Support libs
40
- gem "factory_bot"
44
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.0.0")
45
+ gem "factory_bot"
46
+ else
47
+ gem "factory_bot", "~> 5", "< 6.4.5"
48
+ end
41
49
  gem "timecop"
42
50
  gem "chronic"
43
51
  gem "mocha", "~> 2.1.0"
data/README.markdown CHANGED
@@ -265,7 +265,7 @@ Book.import books, recursive: true
265
265
  Key | Options | Default | Description
266
266
  ------------------------- | --------------------- | ------------------ | -----------
267
267
  :validate | `true`/`false` | `true` | Whether or not to run `ActiveRecord` validations (uniqueness skipped). This option will always be true when using `import!`.
268
- :validate_uniqueness | `true`/`false` | `false` | Whether or not to run uniqueness validations, has potential pitfalls, use with caution (requires `>= v0.27.0`).
268
+ :validate_uniqueness | `true`/`false` | `false` | Whether or not to run ActiveRecord uniqueness validations. Beware this will incur an sql query per-record (N+1 queries). (requires `>= v0.27.0`).
269
269
  :validate_with_context | `Symbol` |`:create`/`:update` | Allows passing an ActiveModel validation context for each model. Default is `:create` for new records and `:update` for existing ones.
270
270
  :track_validation_failures| `true`/`false` | `false` | When this is set to true, `failed_instances` will be an array of arrays, with each inner array having the form `[:index_in_dataset, :object_with_errors]`
271
271
  :on_duplicate_key_ignore | `true`/`false` | `false` | Allows skipping records with duplicate keys. See [here](#duplicate-key-ignore) for more details.
@@ -274,6 +274,7 @@ Key | Options | Default | Descrip
274
274
  :synchronize | `Array` | N/A | An array of ActiveRecord instances. This synchronizes existing instances in memory with updates from the import.
275
275
  :timestamps | `true`/`false` | `true` | Enables/disables timestamps on imported records.
276
276
  :recursive | `true`/`false` | `false` | Imports has_many/has_one associations (PostgreSQL only).
277
+ :recursive_on_duplicate_key_update | `Hash` | N/A | Allows upsert logic to be used for recursive associations. The hash key is the association name and the value has the same options as `:on_duplicate_key_update`. See [here](#duplicate-key-update) for more details.
277
278
  :batch_size | `Integer` | total # of records | Max number of records to insert per import
278
279
  :raise_error | `true`/`false` | `false` | Raises an exception at the first invalid record. This means there will not be a result object returned. The `import!` method is a shortcut for this.
279
280
  :all_or_none | `true`/`false` | `false` | Will not import any records if there is a record with validation errors.
@@ -364,6 +365,29 @@ book.reload.title # => "Book1" (stayed the same)
364
365
  book.reload.author # => "Bob Barker" (changed)
365
366
  ```
366
367
 
368
+ PostgreSQL Using partial indexes
369
+
370
+ ```ruby
371
+ book = Book.create! title: "Book1", author: "George Orwell", published_at: Time.now
372
+ book.author = "Bob Barker"
373
+
374
+ # in migration
375
+ execute <<-SQL
376
+ CREATE INDEX books_published_at_index ON books (published_at) WHERE published_at IS NOT NULL;
377
+ SQL
378
+
379
+ # PostgreSQL version
380
+ Book.import [book], on_duplicate_key_update: {
381
+ conflict_target: [:id],
382
+ index_predicate: "published_at IS NOT NULL",
383
+ columns: [:author]
384
+ }
385
+
386
+ book.reload.title # => "Book1" (stayed the same)
387
+ book.reload.author # => "Bob Barker" (changed)
388
+ book.reload.published_at # => 2017-10-09 (stayed the same)
389
+ ```
390
+
367
391
  PostgreSQL Using constraints
368
392
 
369
393
  ```ruby
data/Rakefile CHANGED
@@ -29,6 +29,7 @@ ADAPTERS = %w(
29
29
  sqlite3
30
30
  spatialite
31
31
  seamless_database_pool
32
+ trilogy
32
33
  ).freeze
33
34
  ADAPTERS.each do |adapter|
34
35
  namespace :test do
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/connection_adapters/trilogy_adapter"
4
+ require "activerecord-import/adapters/trilogy_adapter"
5
+
6
+ class ActiveRecord::ConnectionAdapters::TrilogyAdapter
7
+ include ActiveRecord::Import::TrilogyAdapter
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "activerecord-import/adapters/mysql_adapter"
4
+
5
+ module ActiveRecord::Import::TrilogyAdapter
6
+ include ActiveRecord::Import::MysqlAdapter
7
+ end
@@ -557,7 +557,7 @@ class ActiveRecord::Base
557
557
  options.merge!( args.pop ) if args.last.is_a? Hash
558
558
  # making sure that current model's primary key is used
559
559
  options[:primary_key] = primary_key
560
- options[:locking_column] = locking_column if attribute_names.include?(locking_column)
560
+ options[:locking_column] = locking_column if locking_enabled?
561
561
 
562
562
  is_validating = options[:validate_with_context].present? ? true : options[:validate]
563
563
  validator = ActiveRecord::Import::Validator.new(self, options)
@@ -857,6 +857,15 @@ class ActiveRecord::Base
857
857
 
858
858
  private
859
859
 
860
+ def associated_options(options, associated_class)
861
+ return options unless options.key?(:recursive_on_duplicate_key_update)
862
+
863
+ table_name = associated_class.arel_table.name.to_sym
864
+ options.merge(
865
+ on_duplicate_key_update: options[:recursive_on_duplicate_key_update][table_name]
866
+ )
867
+ end
868
+
860
869
  def set_attributes_and_mark_clean(models, import_result, timestamps, options)
861
870
  return if models.nil?
862
871
  models -= import_result.failed_instances
@@ -941,7 +950,7 @@ class ActiveRecord::Base
941
950
  association = association.target
942
951
  next if association.blank? || model.public_send(column_name).present?
943
952
 
944
- association_primary_key = Array(association_reflection.association_primary_key)[column_index]
953
+ association_primary_key = Array(association_reflection.association_primary_key.tr("[]:", "").split(", "))[column_index]
945
954
  model.public_send("#{column_name}=", association.send(association_primary_key))
946
955
  end
947
956
  end
@@ -963,7 +972,11 @@ class ActiveRecord::Base
963
972
 
964
973
  associated_objects_by_class.each_value do |associations|
965
974
  associations.each_value do |associated_records|
966
- associated_records.first.class.bulk_import(associated_records, options) unless associated_records.empty?
975
+ next if associated_records.empty?
976
+
977
+ associated_class = associated_records.first.class
978
+ associated_class.bulk_import(associated_records,
979
+ associated_options(options, associated_class))
967
980
  end
968
981
  end
969
982
  end
@@ -996,7 +1009,10 @@ class ActiveRecord::Base
996
1009
 
997
1010
  changed_objects = association.select { |a| a.new_record? || a.changed? }
998
1011
  changed_objects.each do |child|
999
- child.public_send("#{association_reflection.foreign_key}=", model.id)
1012
+ Array(association_reflection.inverse_of&.foreign_key || association_reflection.foreign_key).each_with_index do |column, index|
1013
+ child.public_send("#{column}=", Array(model.id)[index])
1014
+ end
1015
+
1000
1016
  # For polymorphic associations
1001
1017
  association_name = if model.class.respond_to?(:polymorphic_name)
1002
1018
  model.class.polymorphic_name
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Import
5
- VERSION = "1.5.1"
5
+ VERSION = "1.7.0"
6
6
  end
7
7
  end
@@ -1,4 +1,3 @@
1
- # rubocop:disable Naming/FileName
2
1
  # frozen_string_literal: true
3
2
 
4
3
  require "active_support/lazy_load_hooks"
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ ENV["ARE_DB"] = "trilogy"
4
+
5
+ if ENV['AR_VERSION'].to_f <= 7.0
6
+ require "activerecord-trilogy-adapter"
7
+ require "trilogy_adapter/connection"
8
+ ActiveRecord::Base.extend TrilogyAdapter::Connection
9
+ end
@@ -52,3 +52,8 @@ sqlite3: &sqlite3
52
52
 
53
53
  spatialite:
54
54
  <<: *sqlite3
55
+
56
+ trilogy:
57
+ <<: *common
58
+ adapter: trilogy
59
+ host: mysql
@@ -66,3 +66,7 @@ sqlite3: &sqlite3
66
66
 
67
67
  spatialite:
68
68
  <<: *sqlite3
69
+
70
+ trilogy:
71
+ <<: *common
72
+ adapter: trilogy
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Author < ActiveRecord::Base
4
+ if ENV['AR_VERSION'].to_f >= 7.1
5
+ has_many :composite_books, query_constraints: [:id, :author_id], inverse_of: :author
6
+ end
7
+ 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] unless ENV["SKIP_COMPOSITE_PK"]
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
@@ -2,9 +2,17 @@
2
2
 
3
3
  class Customer < ActiveRecord::Base
4
4
  unless ENV["SKIP_COMPOSITE_PK"]
5
- has_many :orders,
6
- inverse_of: :customer,
7
- primary_key: %i(account_id id),
8
- foreign_key: %i(account_id customer_id)
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
+
9
17
  end
10
18
  end
data/test/models/order.rb CHANGED
@@ -2,9 +2,16 @@
2
2
 
3
3
  class Order < ActiveRecord::Base
4
4
  unless ENV["SKIP_COMPOSITE_PK"]
5
- belongs_to :customer,
6
- inverse_of: :orders,
7
- primary_key: %i(account_id id),
8
- foreign_key: %i(account_id customer_id)
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
9
16
  end
10
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
@@ -2,6 +2,10 @@
2
2
 
3
3
  class TagAlias < ActiveRecord::Base
4
4
  unless ENV["SKIP_COMPOSITE_PK"]
5
- belongs_to :tag, foreign_key: [:tag_id, :parent_id], required: true
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
6
10
  end
7
11
  end
@@ -57,4 +57,38 @@ ActiveRecord::Schema.define do
57
57
  end
58
58
 
59
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
60
94
  end
@@ -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
@@ -223,6 +223,34 @@ def should_support_basic_on_duplicate_key_update
223
223
  end
224
224
  end
225
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
226
254
  end
227
255
 
228
256
  context "with :on_duplicate_key_update" do
@@ -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
@@ -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
@@ -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
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-import
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.1
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zach Dennis
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-18 00:00:00.000000000 Z
11
+ date: 2024-05-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -87,12 +87,14 @@ files:
87
87
  - lib/activerecord-import/active_record/adapters/postgresql_adapter.rb
88
88
  - lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb
89
89
  - lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb
90
+ - lib/activerecord-import/active_record/adapters/trilogy_adapter.rb
90
91
  - lib/activerecord-import/adapters/abstract_adapter.rb
91
92
  - lib/activerecord-import/adapters/em_mysql2_adapter.rb
92
93
  - lib/activerecord-import/adapters/mysql2_adapter.rb
93
94
  - lib/activerecord-import/adapters/mysql_adapter.rb
94
95
  - lib/activerecord-import/adapters/postgresql_adapter.rb
95
96
  - lib/activerecord-import/adapters/sqlite3_adapter.rb
97
+ - lib/activerecord-import/adapters/trilogy_adapter.rb
96
98
  - lib/activerecord-import/base.rb
97
99
  - lib/activerecord-import/import.rb
98
100
  - lib/activerecord-import/mysql2.rb
@@ -114,6 +116,7 @@ files:
114
116
  - test/adapters/seamless_database_pool.rb
115
117
  - test/adapters/spatialite.rb
116
118
  - test/adapters/sqlite3.rb
119
+ - test/adapters/trilogy.rb
117
120
  - test/database.yml.sample
118
121
  - test/github/database.yml
119
122
  - test/import_test.rb
@@ -124,11 +127,14 @@ files:
124
127
  - test/models/account.rb
125
128
  - test/models/alarm.rb
126
129
  - test/models/animal.rb
130
+ - test/models/author.rb
127
131
  - test/models/bike_maker.rb
128
132
  - test/models/book.rb
129
133
  - test/models/car.rb
130
134
  - test/models/card.rb
131
135
  - test/models/chapter.rb
136
+ - test/models/composite_book.rb
137
+ - test/models/composite_chapter.rb
132
138
  - test/models/customer.rb
133
139
  - test/models/deck.rb
134
140
  - test/models/dictionary.rb
@@ -172,13 +178,14 @@ files:
172
178
  - test/support/sqlite3/import_examples.rb
173
179
  - test/synchronize_test.rb
174
180
  - test/test_helper.rb
181
+ - test/trilogy/import_test.rb
175
182
  - test/value_sets_bytes_parser_test.rb
176
183
  - test/value_sets_records_parser_test.rb
177
184
  homepage: https://github.com/zdennis/activerecord-import
178
185
  licenses:
179
186
  - MIT
180
187
  metadata: {}
181
- post_install_message:
188
+ post_install_message:
182
189
  rdoc_options: []
183
190
  require_paths:
184
191
  - lib
@@ -193,8 +200,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
200
  - !ruby/object:Gem::Version
194
201
  version: '0'
195
202
  requirements: []
196
- rubygems_version: 3.3.25
197
- signing_key:
203
+ rubygems_version: 3.0.3.1
204
+ signing_key:
198
205
  specification_version: 4
199
206
  summary: Bulk insert extension for ActiveRecord
200
207
  test_files:
@@ -211,6 +218,7 @@ test_files:
211
218
  - test/adapters/seamless_database_pool.rb
212
219
  - test/adapters/spatialite.rb
213
220
  - test/adapters/sqlite3.rb
221
+ - test/adapters/trilogy.rb
214
222
  - test/database.yml.sample
215
223
  - test/github/database.yml
216
224
  - test/import_test.rb
@@ -221,11 +229,14 @@ test_files:
221
229
  - test/models/account.rb
222
230
  - test/models/alarm.rb
223
231
  - test/models/animal.rb
232
+ - test/models/author.rb
224
233
  - test/models/bike_maker.rb
225
234
  - test/models/book.rb
226
235
  - test/models/car.rb
227
236
  - test/models/card.rb
228
237
  - test/models/chapter.rb
238
+ - test/models/composite_book.rb
239
+ - test/models/composite_chapter.rb
229
240
  - test/models/customer.rb
230
241
  - test/models/deck.rb
231
242
  - test/models/dictionary.rb
@@ -269,5 +280,6 @@ test_files:
269
280
  - test/support/sqlite3/import_examples.rb
270
281
  - test/synchronize_test.rb
271
282
  - test/test_helper.rb
283
+ - test/trilogy/import_test.rb
272
284
  - test/value_sets_bytes_parser_test.rb
273
285
  - test/value_sets_records_parser_test.rb