activerecord-import 1.5.1 → 1.7.0

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