activerecord-import 1.5.0 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yaml +46 -12
- data/.gitignore +4 -0
- data/.rubocop_todo.yml +4 -0
- data/CHANGELOG.md +29 -0
- data/Dockerfile +23 -0
- data/Gemfile +12 -4
- data/README.markdown +38 -1
- data/Rakefile +1 -0
- data/activerecord-import.gemspec +4 -0
- data/docker-compose.yml +34 -0
- data/gemfiles/7.1.gemfile +3 -0
- data/gemfiles/7.2.gemfile +3 -0
- data/lib/activerecord-import/active_record/adapters/trilogy_adapter.rb +8 -0
- data/lib/activerecord-import/adapters/trilogy_adapter.rb +7 -0
- data/lib/activerecord-import/import.rb +22 -5
- data/lib/activerecord-import/version.rb +1 -1
- data/lib/activerecord-import.rb +0 -1
- data/test/adapters/trilogy.rb +9 -0
- data/test/database.yml.sample +7 -0
- data/test/github/database.yml +4 -0
- data/test/models/author.rb +7 -0
- data/test/models/book.rb +5 -2
- data/test/models/composite_book.rb +19 -0
- data/test/models/composite_chapter.rb +9 -0
- data/test/models/customer.rb +14 -4
- data/test/models/order.rb +13 -4
- data/test/models/tag.rb +6 -1
- data/test/models/tag_alias.rb +7 -1
- data/test/models/topic.rb +1 -1
- data/test/models/widget.rb +10 -3
- data/test/schema/generic_schema.rb +3 -1
- data/test/schema/postgresql_schema.rb +35 -4
- data/test/support/postgresql/import_examples.rb +12 -0
- data/test/support/shared_examples/on_duplicate_key_update.rb +28 -0
- data/test/support/shared_examples/recursive_import.rb +67 -1
- data/test/test_helper.rb +3 -1
- data/test/trilogy/import_test.rb +7 -0
- metadata +24 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1478b29daf1e757c91a0ca8693bb1b03f9a90daa2fe02f0959576bcec900d85
|
4
|
+
data.tar.gz: dc938a1936c931182675a118ad24a37e2a7c5a27c0419e5bd947e0ec35aa4334
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e276196113f433c8989a9a06cfad36df650105183a5559a548bdd8a04bd2da22db36ca996d2d0d04c4561b33ceb44559b50b4ad98e0a2d9f050b8084ee99c8e6
|
7
|
+
data.tar.gz: e699052ac2499bac23a9955fef71df853050767785d573523e23a1d216cfba9cb75b81b0db9f5e2afac02afbcb416563af6e7a2bf69f6d1288c6e6c71aeb7e5c
|
data/.github/workflows/test.yaml
CHANGED
@@ -16,17 +16,50 @@ 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.
|
37
|
+
- 3.3
|
24
38
|
env:
|
39
|
+
- AR_VERSION: '7.2'
|
40
|
+
RUBYOPT: --enable-frozen-string-literal
|
41
|
+
- AR_VERSION: '7.1'
|
42
|
+
RUBYOPT: --enable-frozen-string-literal
|
25
43
|
- AR_VERSION: '7.0'
|
26
44
|
RUBYOPT: --enable-frozen-string-literal
|
27
45
|
- AR_VERSION: 6.1
|
28
46
|
RUBYOPT: --enable-frozen-string-literal
|
29
47
|
include:
|
48
|
+
- ruby: 3.2
|
49
|
+
env:
|
50
|
+
AR_VERSION: '7.2'
|
51
|
+
- ruby: 3.2
|
52
|
+
env:
|
53
|
+
AR_VERSION: '7.1'
|
54
|
+
- ruby: 3.2
|
55
|
+
env:
|
56
|
+
AR_VERSION: '7.0'
|
57
|
+
- ruby: 3.2
|
58
|
+
env:
|
59
|
+
AR_VERSION: 6.1
|
60
|
+
- ruby: 3.1
|
61
|
+
env:
|
62
|
+
AR_VERSION: '7.1'
|
30
63
|
- ruby: 3.1
|
31
64
|
env:
|
32
65
|
AR_VERSION: '7.0'
|
@@ -39,6 +72,9 @@ jobs:
|
|
39
72
|
- ruby: '3.0'
|
40
73
|
env:
|
41
74
|
AR_VERSION: 6.1
|
75
|
+
- ruby: jruby-9.4.5.0
|
76
|
+
env:
|
77
|
+
AR_VERSION: '7.0'
|
42
78
|
- ruby: 2.7
|
43
79
|
env:
|
44
80
|
AR_VERSION: '7.0'
|
@@ -48,28 +84,23 @@ jobs:
|
|
48
84
|
- ruby: 2.7
|
49
85
|
env:
|
50
86
|
AR_VERSION: '6.0'
|
51
|
-
- ruby:
|
87
|
+
- ruby: jruby-9.3.10.0
|
52
88
|
env:
|
53
|
-
AR_VERSION:
|
89
|
+
AR_VERSION: '6.1'
|
54
90
|
- ruby: 2.6
|
55
91
|
env:
|
56
|
-
AR_VERSION: 5.
|
57
|
-
- ruby: 2.4
|
58
|
-
env:
|
59
|
-
AR_VERSION: '5.0'
|
60
|
-
- ruby: 2.4
|
61
|
-
env:
|
62
|
-
AR_VERSION: 4.2
|
92
|
+
AR_VERSION: 5.2
|
63
93
|
runs-on: ubuntu-latest
|
64
94
|
env:
|
65
95
|
AR_VERSION: ${{ matrix.env.AR_VERSION }}
|
66
96
|
DB_DATABASE: activerecord_import_test
|
67
97
|
steps:
|
68
|
-
- uses: actions/checkout@
|
98
|
+
- uses: actions/checkout@v4
|
69
99
|
- uses: ruby/setup-ruby@v1
|
70
100
|
with:
|
71
101
|
ruby-version: ${{ matrix.ruby }}
|
72
102
|
bundler-cache: true
|
103
|
+
rubygems: latest
|
73
104
|
- name: Set up databases
|
74
105
|
run: |
|
75
106
|
sudo /etc/init.d/mysql start
|
@@ -99,12 +130,15 @@ jobs:
|
|
99
130
|
run: |
|
100
131
|
bundle exec rake test:spatialite
|
101
132
|
bundle exec rake test:sqlite3
|
133
|
+
- name: Run trilogy tests
|
134
|
+
if: ${{ matrix.env.AR_VERSION >= '7.0' && !startsWith(matrix.ruby, 'jruby') }}
|
135
|
+
run: bundle exec rake test:trilogy
|
102
136
|
lint:
|
103
137
|
runs-on: ubuntu-latest
|
104
138
|
env:
|
105
139
|
AR_VERSION: '7.0'
|
106
140
|
steps:
|
107
|
-
- uses: actions/checkout@
|
141
|
+
- uses: actions/checkout@v4
|
108
142
|
- uses: ruby/setup-ruby@v1
|
109
143
|
with:
|
110
144
|
ruby-version: 2.7
|
data/.gitignore
CHANGED
data/.rubocop_todo.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,32 @@
|
|
1
|
+
## Changes in 1.8.0
|
2
|
+
|
3
|
+
### New Features
|
4
|
+
|
5
|
+
* Add support for ActiveRecord 7.2 via \##845.
|
6
|
+
|
7
|
+
## Changes in 1.7.0
|
8
|
+
|
9
|
+
### New Features
|
10
|
+
|
11
|
+
* Add support for ActiveRecord 7.1 composite primary keys. Thanks to @fragkakis via \##837.
|
12
|
+
* Add support for upserting associations when doing recursive imports. Thanks to @ramblex via \##778.
|
13
|
+
|
14
|
+
## Changes in 1.6.0
|
15
|
+
|
16
|
+
### New Features
|
17
|
+
|
18
|
+
* Add trilogy adapter support. Thanks to @zmariscal via \##825.
|
19
|
+
|
20
|
+
### Fixes
|
21
|
+
|
22
|
+
* Use the locking_enabled? method provided by activerecord to decide whether the lock field should be updated. Thanks to @dombesz via \##822.
|
23
|
+
|
24
|
+
## Changes in 1.5.1
|
25
|
+
|
26
|
+
### Fixes
|
27
|
+
|
28
|
+
* Stop memoizing schema_columns_hash so dynamic schema changes are picked up. Thanks to @koshigoe via \##812.
|
29
|
+
|
1
30
|
## Changes in 1.5.0
|
2
31
|
|
3
32
|
### New Features
|
data/Dockerfile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Use the official Ruby 3.2 image as a base image
|
2
|
+
ARG RUBY_VERSION=3.2
|
3
|
+
FROM ruby:${RUBY_VERSION}-bullseye
|
4
|
+
|
5
|
+
# Set the working directory
|
6
|
+
WORKDIR /usr/src/app
|
7
|
+
|
8
|
+
# Install system packages
|
9
|
+
RUN apt-get update -qq && \
|
10
|
+
apt-get install -y default-mysql-client postgresql postgresql-contrib vim && \
|
11
|
+
apt-get clean
|
12
|
+
|
13
|
+
# Set environment variables
|
14
|
+
ENV AR_VERSION=7.0
|
15
|
+
|
16
|
+
# Copy all files
|
17
|
+
COPY . .
|
18
|
+
|
19
|
+
# Move sample database.yml and install gems
|
20
|
+
RUN mv test/database.yml.sample test/database.yml && \
|
21
|
+
bundle install
|
22
|
+
|
23
|
+
CMD ["irb"]
|
data/Gemfile
CHANGED
@@ -26,18 +26,26 @@ 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
|
32
36
|
gem "jdbc-mysql"
|
33
37
|
gem "jdbc-postgres"
|
34
|
-
gem "activerecord-jdbcsqlite3-adapter"
|
35
|
-
gem "activerecord-jdbcmysql-adapter"
|
36
|
-
gem "activerecord-jdbcpostgresql-adapter"
|
38
|
+
gem "activerecord-jdbcsqlite3-adapter"
|
39
|
+
gem "activerecord-jdbcmysql-adapter"
|
40
|
+
gem "activerecord-jdbcpostgresql-adapter"
|
37
41
|
end
|
38
42
|
|
39
43
|
# Support libs
|
40
|
-
|
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
|
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
|
@@ -626,6 +650,19 @@ AR_VERSION=7.0 bundle exec rake test:postgresql test:sqlite3 test:mysql2
|
|
626
650
|
|
627
651
|
Once you have pushed up your changes, you can find your CI results [here](https://github.com/zdennis/activerecord-import/actions).
|
628
652
|
|
653
|
+
#### Docker Setup
|
654
|
+
|
655
|
+
Before you begin, make sure you have [Docker](https://www.docker.com/products/docker-desktop/) and [Docker Compose](https://docs.docker.com/compose/) installed on your machine. If you don't, you can install both via Homebrew using the following command:
|
656
|
+
|
657
|
+
```bash
|
658
|
+
brew install docker && brew install docker-compose
|
659
|
+
```
|
660
|
+
##### Steps
|
661
|
+
|
662
|
+
1. In your terminal run `docker-compose up --build`
|
663
|
+
1. In another tab/window run `docker-compose exec app bash`
|
664
|
+
1. In that same terminal run the mysql2 test by running `bundle exec rake test:mysql2`
|
665
|
+
|
629
666
|
## Issue Triage [![Open Source Helpers](https://www.codetriage.com/zdennis/activerecord-import/badges/users.svg)](https://www.codetriage.com/zdennis/activerecord-import)
|
630
667
|
|
631
668
|
You can triage issues which may include reproducing bug reports or asking for vital information, such as version numbers or reproduction instructions. If you would like to start triaging issues, one easy way to get started is to [subscribe to activerecord-import on CodeTriage](https://www.codetriage.com/zdennis/activerecord-import).
|
data/Rakefile
CHANGED
data/activerecord-import.gemspec
CHANGED
@@ -10,6 +10,10 @@ Gem::Specification.new do |gem|
|
|
10
10
|
gem.homepage = "https://github.com/zdennis/activerecord-import"
|
11
11
|
gem.license = "MIT"
|
12
12
|
|
13
|
+
gem.metadata = {
|
14
|
+
"changelog_uri" => "https://github.com/zdennis/activerecord-import/blob/master/CHANGELOG.md"
|
15
|
+
}
|
16
|
+
|
13
17
|
gem.files = `git ls-files`.split($\)
|
14
18
|
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
15
19
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
version: "3.5"
|
2
|
+
|
3
|
+
services:
|
4
|
+
mysql:
|
5
|
+
platform: linux/x86_64
|
6
|
+
image: mysql:5.7
|
7
|
+
volumes:
|
8
|
+
- mysql-data:/var/lib/mysql
|
9
|
+
ports:
|
10
|
+
- "3306:3306"
|
11
|
+
|
12
|
+
postgresql:
|
13
|
+
image: postgres:latest
|
14
|
+
volumes:
|
15
|
+
- postgresql-data:/var/lib/postgresql/data
|
16
|
+
ports:
|
17
|
+
- "5432:5432"
|
18
|
+
|
19
|
+
app:
|
20
|
+
build:
|
21
|
+
context: .
|
22
|
+
environment:
|
23
|
+
DB_HOST: mysql
|
24
|
+
AR_VERSION: 7.0
|
25
|
+
volumes:
|
26
|
+
- .:/usr/src/app
|
27
|
+
depends_on:
|
28
|
+
- mysql
|
29
|
+
- postgresql
|
30
|
+
command: tail -f /dev/null
|
31
|
+
|
32
|
+
volumes:
|
33
|
+
mysql-data:
|
34
|
+
postgresql-data:
|
@@ -62,6 +62,7 @@ module ActiveRecord::Import # :nodoc:
|
|
62
62
|
if @validate_callbacks.respond_to?(:chain, true)
|
63
63
|
@validate_callbacks.send(:chain).tap do |chain|
|
64
64
|
callback.instance_variable_set(:@filter, filter)
|
65
|
+
callback.instance_variable_set(:@compiled, nil)
|
65
66
|
chain[i] = callback
|
66
67
|
end
|
67
68
|
else
|
@@ -557,7 +558,7 @@ class ActiveRecord::Base
|
|
557
558
|
options.merge!( args.pop ) if args.last.is_a? Hash
|
558
559
|
# making sure that current model's primary key is used
|
559
560
|
options[:primary_key] = primary_key
|
560
|
-
options[:locking_column] = locking_column if
|
561
|
+
options[:locking_column] = locking_column if locking_enabled?
|
561
562
|
|
562
563
|
is_validating = options[:validate_with_context].present? ? true : options[:validate]
|
563
564
|
validator = ActiveRecord::Import::Validator.new(self, options)
|
@@ -857,6 +858,15 @@ class ActiveRecord::Base
|
|
857
858
|
|
858
859
|
private
|
859
860
|
|
861
|
+
def associated_options(options, associated_class)
|
862
|
+
return options unless options.key?(:recursive_on_duplicate_key_update)
|
863
|
+
|
864
|
+
table_name = associated_class.arel_table.name.to_sym
|
865
|
+
options.merge(
|
866
|
+
on_duplicate_key_update: options[:recursive_on_duplicate_key_update][table_name]
|
867
|
+
)
|
868
|
+
end
|
869
|
+
|
860
870
|
def set_attributes_and_mark_clean(models, import_result, timestamps, options)
|
861
871
|
return if models.nil?
|
862
872
|
models -= import_result.failed_instances
|
@@ -941,7 +951,7 @@ class ActiveRecord::Base
|
|
941
951
|
association = association.target
|
942
952
|
next if association.blank? || model.public_send(column_name).present?
|
943
953
|
|
944
|
-
association_primary_key = Array(association_reflection.association_primary_key)[column_index]
|
954
|
+
association_primary_key = Array(association_reflection.association_primary_key.tr("[]:", "").split(", "))[column_index]
|
945
955
|
model.public_send("#{column_name}=", association.send(association_primary_key))
|
946
956
|
end
|
947
957
|
end
|
@@ -963,13 +973,17 @@ class ActiveRecord::Base
|
|
963
973
|
|
964
974
|
associated_objects_by_class.each_value do |associations|
|
965
975
|
associations.each_value do |associated_records|
|
966
|
-
|
976
|
+
next if associated_records.empty?
|
977
|
+
|
978
|
+
associated_class = associated_records.first.class
|
979
|
+
associated_class.bulk_import(associated_records,
|
980
|
+
associated_options(options, associated_class))
|
967
981
|
end
|
968
982
|
end
|
969
983
|
end
|
970
984
|
|
971
985
|
def schema_columns_hash
|
972
|
-
|
986
|
+
if respond_to?(:ignored_columns) && ignored_columns.any?
|
973
987
|
connection.schema_cache.columns_hash(table_name)
|
974
988
|
else
|
975
989
|
columns_hash
|
@@ -996,7 +1010,10 @@ class ActiveRecord::Base
|
|
996
1010
|
|
997
1011
|
changed_objects = association.select { |a| a.new_record? || a.changed? }
|
998
1012
|
changed_objects.each do |child|
|
999
|
-
|
1013
|
+
Array(association_reflection.inverse_of&.foreign_key || association_reflection.foreign_key).each_with_index do |column, index|
|
1014
|
+
child.public_send("#{column}=", Array(model.id)[index])
|
1015
|
+
end
|
1016
|
+
|
1000
1017
|
# For polymorphic associations
|
1001
1018
|
association_name = if model.class.respond_to?(:polymorphic_name)
|
1002
1019
|
model.class.polymorphic_name
|
data/lib/activerecord-import.rb
CHANGED
data/test/database.yml.sample
CHANGED
@@ -8,6 +8,7 @@ common: &common
|
|
8
8
|
mysql2: &mysql2
|
9
9
|
<<: *common
|
10
10
|
adapter: mysql2
|
11
|
+
host: mysql
|
11
12
|
|
12
13
|
mysql2spatial:
|
13
14
|
<<: *mysql2
|
@@ -19,6 +20,7 @@ postgresql: &postgresql
|
|
19
20
|
<<: *common
|
20
21
|
username: postgres
|
21
22
|
adapter: postgresql
|
23
|
+
host: postgresql
|
22
24
|
min_messages: warning
|
23
25
|
|
24
26
|
postresql_makara:
|
@@ -50,3 +52,8 @@ sqlite3: &sqlite3
|
|
50
52
|
|
51
53
|
spatialite:
|
52
54
|
<<: *sqlite3
|
55
|
+
|
56
|
+
trilogy:
|
57
|
+
<<: *common
|
58
|
+
adapter: trilogy
|
59
|
+
host: mysql
|
data/test/github/database.yml
CHANGED
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
|
-
|
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
|
data/test/models/customer.rb
CHANGED
@@ -1,8 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Customer < ActiveRecord::Base
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
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
|
data/test/models/tag_alias.rb
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class TagAlias < ActiveRecord::Base
|
4
|
-
|
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
data/test/models/widget.rb
CHANGED
@@ -19,8 +19,15 @@ class Widget < ActiveRecord::Base
|
|
19
19
|
|
20
20
|
default_scope -> { where(active: true) }
|
21
21
|
|
22
|
-
|
23
|
-
|
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
|
@@ -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
|
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|
|
@@ -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
|
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
|
@@ -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
|
@@ -147,7 +147,7 @@ def should_support_recursive_import
|
|
147
147
|
end
|
148
148
|
|
149
149
|
books.each do |book|
|
150
|
-
|
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
@@ -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.
|
4
|
+
version: 1.8.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:
|
11
|
+
date: 2024-08-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -51,6 +51,7 @@ files:
|
|
51
51
|
- ".rubocop_todo.yml"
|
52
52
|
- Brewfile
|
53
53
|
- CHANGELOG.md
|
54
|
+
- Dockerfile
|
54
55
|
- Gemfile
|
55
56
|
- LICENSE
|
56
57
|
- README.markdown
|
@@ -68,6 +69,7 @@ files:
|
|
68
69
|
- benchmarks/models/test_memory.rb
|
69
70
|
- benchmarks/models/test_myisam.rb
|
70
71
|
- benchmarks/schema/mysql2_schema.rb
|
72
|
+
- docker-compose.yml
|
71
73
|
- gemfiles/4.2.gemfile
|
72
74
|
- gemfiles/5.0.gemfile
|
73
75
|
- gemfiles/5.1.gemfile
|
@@ -75,6 +77,8 @@ files:
|
|
75
77
|
- gemfiles/6.0.gemfile
|
76
78
|
- gemfiles/6.1.gemfile
|
77
79
|
- gemfiles/7.0.gemfile
|
80
|
+
- gemfiles/7.1.gemfile
|
81
|
+
- gemfiles/7.2.gemfile
|
78
82
|
- lib/activerecord-import.rb
|
79
83
|
- lib/activerecord-import/active_record/adapters/abstract_adapter.rb
|
80
84
|
- lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb
|
@@ -84,12 +88,14 @@ files:
|
|
84
88
|
- lib/activerecord-import/active_record/adapters/postgresql_adapter.rb
|
85
89
|
- lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb
|
86
90
|
- lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb
|
91
|
+
- lib/activerecord-import/active_record/adapters/trilogy_adapter.rb
|
87
92
|
- lib/activerecord-import/adapters/abstract_adapter.rb
|
88
93
|
- lib/activerecord-import/adapters/em_mysql2_adapter.rb
|
89
94
|
- lib/activerecord-import/adapters/mysql2_adapter.rb
|
90
95
|
- lib/activerecord-import/adapters/mysql_adapter.rb
|
91
96
|
- lib/activerecord-import/adapters/postgresql_adapter.rb
|
92
97
|
- lib/activerecord-import/adapters/sqlite3_adapter.rb
|
98
|
+
- lib/activerecord-import/adapters/trilogy_adapter.rb
|
93
99
|
- lib/activerecord-import/base.rb
|
94
100
|
- lib/activerecord-import/import.rb
|
95
101
|
- lib/activerecord-import/mysql2.rb
|
@@ -111,6 +117,7 @@ files:
|
|
111
117
|
- test/adapters/seamless_database_pool.rb
|
112
118
|
- test/adapters/spatialite.rb
|
113
119
|
- test/adapters/sqlite3.rb
|
120
|
+
- test/adapters/trilogy.rb
|
114
121
|
- test/database.yml.sample
|
115
122
|
- test/github/database.yml
|
116
123
|
- test/import_test.rb
|
@@ -121,11 +128,14 @@ files:
|
|
121
128
|
- test/models/account.rb
|
122
129
|
- test/models/alarm.rb
|
123
130
|
- test/models/animal.rb
|
131
|
+
- test/models/author.rb
|
124
132
|
- test/models/bike_maker.rb
|
125
133
|
- test/models/book.rb
|
126
134
|
- test/models/car.rb
|
127
135
|
- test/models/card.rb
|
128
136
|
- test/models/chapter.rb
|
137
|
+
- test/models/composite_book.rb
|
138
|
+
- test/models/composite_chapter.rb
|
129
139
|
- test/models/customer.rb
|
130
140
|
- test/models/deck.rb
|
131
141
|
- test/models/dictionary.rb
|
@@ -169,13 +179,15 @@ files:
|
|
169
179
|
- test/support/sqlite3/import_examples.rb
|
170
180
|
- test/synchronize_test.rb
|
171
181
|
- test/test_helper.rb
|
182
|
+
- test/trilogy/import_test.rb
|
172
183
|
- test/value_sets_bytes_parser_test.rb
|
173
184
|
- test/value_sets_records_parser_test.rb
|
174
185
|
homepage: https://github.com/zdennis/activerecord-import
|
175
186
|
licenses:
|
176
187
|
- MIT
|
177
|
-
metadata:
|
178
|
-
|
188
|
+
metadata:
|
189
|
+
changelog_uri: https://github.com/zdennis/activerecord-import/blob/master/CHANGELOG.md
|
190
|
+
post_install_message:
|
179
191
|
rdoc_options: []
|
180
192
|
require_paths:
|
181
193
|
- lib
|
@@ -190,8 +202,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
190
202
|
- !ruby/object:Gem::Version
|
191
203
|
version: '0'
|
192
204
|
requirements: []
|
193
|
-
rubygems_version: 3.
|
194
|
-
signing_key:
|
205
|
+
rubygems_version: 3.5.11
|
206
|
+
signing_key:
|
195
207
|
specification_version: 4
|
196
208
|
summary: Bulk insert extension for ActiveRecord
|
197
209
|
test_files:
|
@@ -208,6 +220,7 @@ test_files:
|
|
208
220
|
- test/adapters/seamless_database_pool.rb
|
209
221
|
- test/adapters/spatialite.rb
|
210
222
|
- test/adapters/sqlite3.rb
|
223
|
+
- test/adapters/trilogy.rb
|
211
224
|
- test/database.yml.sample
|
212
225
|
- test/github/database.yml
|
213
226
|
- test/import_test.rb
|
@@ -218,11 +231,14 @@ test_files:
|
|
218
231
|
- test/models/account.rb
|
219
232
|
- test/models/alarm.rb
|
220
233
|
- test/models/animal.rb
|
234
|
+
- test/models/author.rb
|
221
235
|
- test/models/bike_maker.rb
|
222
236
|
- test/models/book.rb
|
223
237
|
- test/models/car.rb
|
224
238
|
- test/models/card.rb
|
225
239
|
- test/models/chapter.rb
|
240
|
+
- test/models/composite_book.rb
|
241
|
+
- test/models/composite_chapter.rb
|
226
242
|
- test/models/customer.rb
|
227
243
|
- test/models/deck.rb
|
228
244
|
- test/models/dictionary.rb
|
@@ -266,5 +282,6 @@ test_files:
|
|
266
282
|
- test/support/sqlite3/import_examples.rb
|
267
283
|
- test/synchronize_test.rb
|
268
284
|
- test/test_helper.rb
|
285
|
+
- test/trilogy/import_test.rb
|
269
286
|
- test/value_sets_bytes_parser_test.rb
|
270
287
|
- test/value_sets_records_parser_test.rb
|