activerecord-import 1.8.0 → 2.0.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 +17 -5
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +20 -0
- data/Gemfile +5 -11
- data/README.markdown +2 -2
- data/benchmarks/benchmark.rb +0 -2
- data/benchmarks/lib/base.rb +6 -3
- data/benchmarks/lib/cli_parser.rb +8 -6
- data/gemfiles/8.0.gemfile +3 -0
- data/lib/activerecord-import/import.rb +7 -9
- data/lib/activerecord-import/version.rb +1 -1
- data/test/github/database.yml +2 -2
- data/test/import_test.rb +15 -39
- data/test/models/author.rb +3 -1
- data/test/models/book.rb +6 -2
- data/test/models/composite_book.rb +1 -1
- data/test/models/composite_chapter.rb +4 -1
- data/test/models/customer.rb +1 -1
- data/test/models/order.rb +1 -1
- data/test/models/tag_alias.rb +1 -1
- data/test/models/topic.rb +1 -0
- data/test/support/active_support/test_case_extensions.rb +1 -5
- data/test/support/mysql/import_examples.rb +6 -8
- data/test/support/postgresql/import_examples.rb +37 -53
- data/test/support/shared_examples/recursive_import.rb +39 -0
- data/test/test_helper.rb +7 -20
- metadata +7 -12
- data/gemfiles/4.2.gemfile +0 -4
- data/gemfiles/5.0.gemfile +0 -4
- data/gemfiles/5.1.gemfile +0 -4
- data/lib/activerecord-import/mysql2.rb +0 -9
- data/lib/activerecord-import/postgresql.rb +0 -9
- data/lib/activerecord-import/sqlite3.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f55c9faf85e948fbeb480ebca5baeca11b2275a626bc6ba7517e9d3e8c37e9e7
|
4
|
+
data.tar.gz: 80b267b08ef3a10bb91b029401e8fb6b49fdfcadeeb29a2e5e18fd6c8f529192
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f838da07331afe7827ad7d4f323ebf3f2191decd59a82359393f20eee866345258abb979875dd1c9b853533099797cdf7599ded20ca4e07997f8bce491f4c03e
|
7
|
+
data.tar.gz: 5a257bacb43492f25a65ecc382fe8ad126c4dac474eae7ee2beb3ba92c2cb95e64070a40cd20c48e47f46b51e64b69c2addd76a15fed75863df2ecb4a0fbdb70
|
data/.github/workflows/test.yaml
CHANGED
@@ -21,6 +21,7 @@ jobs:
|
|
21
21
|
ports:
|
22
22
|
- 3306:3306
|
23
23
|
env:
|
24
|
+
MYSQL_HOST: 127.0.0.1
|
24
25
|
MYSQL_ROOT_PASSWORD: root
|
25
26
|
MYSQL_USER: github
|
26
27
|
MYSQL_PASSWORD: github
|
@@ -36,6 +37,8 @@ jobs:
|
|
36
37
|
ruby:
|
37
38
|
- 3.3
|
38
39
|
env:
|
40
|
+
- AR_VERSION: '8.0'
|
41
|
+
RUBYOPT: --enable-frozen-string-literal
|
39
42
|
- AR_VERSION: '7.2'
|
40
43
|
RUBYOPT: --enable-frozen-string-literal
|
41
44
|
- AR_VERSION: '7.1'
|
@@ -45,6 +48,9 @@ jobs:
|
|
45
48
|
- AR_VERSION: 6.1
|
46
49
|
RUBYOPT: --enable-frozen-string-literal
|
47
50
|
include:
|
51
|
+
- ruby: 3.2
|
52
|
+
env:
|
53
|
+
AR_VERSION: '8.0'
|
48
54
|
- ruby: 3.2
|
49
55
|
env:
|
50
56
|
AR_VERSION: '7.2'
|
@@ -72,7 +78,7 @@ jobs:
|
|
72
78
|
- ruby: '3.0'
|
73
79
|
env:
|
74
80
|
AR_VERSION: 6.1
|
75
|
-
- ruby: jruby-9.4.
|
81
|
+
- ruby: jruby-9.4.8.0
|
76
82
|
env:
|
77
83
|
AR_VERSION: '7.0'
|
78
84
|
- ruby: 2.7
|
@@ -84,7 +90,7 @@ jobs:
|
|
84
90
|
- ruby: 2.7
|
85
91
|
env:
|
86
92
|
AR_VERSION: '6.0'
|
87
|
-
- ruby: jruby-9.3.
|
93
|
+
- ruby: jruby-9.3.15.0
|
88
94
|
env:
|
89
95
|
AR_VERSION: '6.1'
|
90
96
|
- ruby: 2.6
|
@@ -96,6 +102,10 @@ jobs:
|
|
96
102
|
DB_DATABASE: activerecord_import_test
|
97
103
|
steps:
|
98
104
|
- uses: actions/checkout@v4
|
105
|
+
- name: Install SQLite3 Development Library
|
106
|
+
run: |
|
107
|
+
sudo apt-get update
|
108
|
+
sudo apt-get install libsqlite3-dev
|
99
109
|
- uses: ruby/setup-ruby@v1
|
100
110
|
with:
|
101
111
|
ruby-version: ${{ matrix.ruby }}
|
@@ -103,8 +113,6 @@ jobs:
|
|
103
113
|
rubygems: latest
|
104
114
|
- name: Set up databases
|
105
115
|
run: |
|
106
|
-
sudo /etc/init.d/mysql start
|
107
|
-
mysql -e 'CREATE DATABASE ${{ env.DB_DATABASE }} CHARACTER SET utf8 COLLATE utf8_general_ci;' -u root -proot
|
108
116
|
psql -h localhost -U postgres -c 'create database ${{ env.DB_DATABASE }};'
|
109
117
|
psql -h localhost -U postgres -d ${{ env.DB_DATABASE }} -c 'create extension if not exists hstore;'
|
110
118
|
psql -h localhost -U postgres -c 'create extension if not exists postgis;'
|
@@ -139,9 +147,13 @@ jobs:
|
|
139
147
|
AR_VERSION: '7.0'
|
140
148
|
steps:
|
141
149
|
- uses: actions/checkout@v4
|
150
|
+
- name: Install SQLite3 Development Library
|
151
|
+
run: |
|
152
|
+
sudo apt-get update
|
153
|
+
sudo apt-get install libsqlite3-dev
|
142
154
|
- uses: ruby/setup-ruby@v1
|
143
155
|
with:
|
144
|
-
ruby-version:
|
156
|
+
ruby-version: 3.0
|
145
157
|
bundler-cache: true
|
146
158
|
- name: Run Rubocop
|
147
159
|
run: bundle exec rubocop
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
+
## Changes in 2.0.0
|
2
|
+
|
3
|
+
### Breaking Changes
|
4
|
+
|
5
|
+
* Fix `recursive_on_duplicate_key_update` doesn't work with non-standard
|
6
|
+
association name. Thanks to @jacob-carlborg-apoex via \#852. The documentation for the
|
7
|
+
`:recursive_on_duplicate_key_update` option specifies that the hash key is
|
8
|
+
the association name. But previously the name of associated table was used to
|
9
|
+
look up the options. Now the behavior matches the documentation and the name
|
10
|
+
of the association is used instead. This only affects associations that uses
|
11
|
+
a name that doesn't follow the ActiveRecord naming conventions of
|
12
|
+
associations and class names, i.e. when the `class_name:` option is used on
|
13
|
+
an association.
|
14
|
+
|
15
|
+
## Changes in 1.8.1
|
16
|
+
|
17
|
+
### Fixes
|
18
|
+
|
19
|
+
* Further update for ActiveRecord 7.2 compatibility when running validations. Thanks to @denisahearn via \##847.
|
20
|
+
|
1
21
|
## Changes in 1.8.0
|
2
22
|
|
3
23
|
### New Features
|
data/Gemfile
CHANGED
@@ -6,13 +6,15 @@ gemspec
|
|
6
6
|
|
7
7
|
version = ENV['AR_VERSION'].to_f
|
8
8
|
|
9
|
-
mysql2_version = '0.
|
10
|
-
mysql2_version = '0.4.0' if version >= 4.2
|
9
|
+
mysql2_version = '0.4.0'
|
11
10
|
mysql2_version = '0.5.0' if version >= 6.1
|
11
|
+
mysql2_version = '0.5.6' if version >= 8.0
|
12
12
|
sqlite3_version = '1.3.0'
|
13
13
|
sqlite3_version = '1.4.0' if version >= 6.0
|
14
|
+
sqlite3_version = '2.2.0' if version >= 8.0
|
14
15
|
pg_version = '0.9'
|
15
16
|
pg_version = '1.1' if version >= 6.1
|
17
|
+
pg_version = '1.5' if version >= 8.0
|
16
18
|
|
17
19
|
group :development, :test do
|
18
20
|
gem 'rubocop'
|
@@ -51,19 +53,11 @@ gem "chronic"
|
|
51
53
|
gem "mocha", "~> 2.1.0"
|
52
54
|
|
53
55
|
# Debugging
|
54
|
-
platforms :jruby do
|
55
|
-
gem "ruby-debug", "= 0.10.4"
|
56
|
-
end
|
57
|
-
|
58
56
|
platforms :ruby do
|
59
57
|
gem "pry-byebug"
|
60
58
|
gem "pry", "~> 0.14.0"
|
61
59
|
end
|
62
60
|
|
63
|
-
|
64
|
-
gem "minitest"
|
65
|
-
else
|
66
|
-
gem "test-unit"
|
67
|
-
end
|
61
|
+
gem "minitest"
|
68
62
|
|
69
63
|
eval_gemfile File.expand_path("../gemfiles/#{version}.gemfile", __FILE__)
|
data/README.markdown
CHANGED
@@ -569,11 +569,11 @@ require 'activerecord-import'
|
|
569
569
|
### Load Path Setup
|
570
570
|
To understand how rubygems loads code you can reference the following:
|
571
571
|
|
572
|
-
|
572
|
+
https://guides.rubygems.org/patterns/#loading-code
|
573
573
|
|
574
574
|
And an example of how active_record dynamically load adapters:
|
575
575
|
|
576
|
-
https://github.com/rails/rails/blob/
|
576
|
+
https://github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters.rb
|
577
577
|
|
578
578
|
In summary, when a gem is loaded rubygems adds the `lib` folder of the gem to the global load path `$LOAD_PATH` so that all `require` lookups will not propagate through all of the folders on the load path. When a `require` is issued each folder on the `$LOAD_PATH` is checked for the file and/or folder referenced. This allows a gem (like activerecord-import) to define push the activerecord-import folder (or namespace) on the `$LOAD_PATH` and any adapters provided by activerecord-import will be found by rubygems when the require is issued.
|
579
579
|
|
data/benchmarks/benchmark.rb
CHANGED
@@ -44,8 +44,6 @@ require adapter_schema if File.exist?(adapter_schema)
|
|
44
44
|
Dir["#{File.dirname(__FILE__)}/models/*.rb"].sort.each { |file| require file }
|
45
45
|
|
46
46
|
require File.join( benchmark_dir, 'lib', "#{options.adapter}_benchmark" )
|
47
|
-
|
48
|
-
table_types = nil
|
49
47
|
table_types = if options.benchmark_all_types
|
50
48
|
["all"]
|
51
49
|
else
|
data/benchmarks/lib/base.rb
CHANGED
@@ -16,7 +16,7 @@ class BenchmarkBase
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
# Returns
|
19
|
+
# Returns a struct which contains two attritues, +description+ and +tms+ after performing an
|
20
20
|
# actual benchmark.
|
21
21
|
#
|
22
22
|
# == PARAMETERS
|
@@ -24,9 +24,12 @@ class BenchmarkBase
|
|
24
24
|
# * blk - the block of code to benchmark
|
25
25
|
#
|
26
26
|
# == RETURNS
|
27
|
-
#
|
27
|
+
# A struct object with the following attributes:
|
28
28
|
# * description - the description of the benchmark ran
|
29
29
|
# * tms - a Benchmark::Tms containing the results of the benchmark
|
30
|
+
|
31
|
+
BmStruct = Struct.new( :description, :tms, :failed, keyword_init: true )
|
32
|
+
|
30
33
|
def bm( description, &block )
|
31
34
|
tms = nil
|
32
35
|
puts "Benchmarking #{description}"
|
@@ -35,7 +38,7 @@ class BenchmarkBase
|
|
35
38
|
delete_all
|
36
39
|
failed = false
|
37
40
|
|
38
|
-
|
41
|
+
BmStruct.new( description: description, tms: tms, failed: failed )
|
39
42
|
end
|
40
43
|
|
41
44
|
# Given a model class (ie: Topic), and an array of columns and value sets
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'optparse'
|
4
|
-
require 'ostruct'
|
5
4
|
|
6
5
|
#
|
7
6
|
# == PARAMETERS
|
@@ -10,7 +9,7 @@ require 'ostruct'
|
|
10
9
|
# * t - the table types to test. ie: myisam, innodb, memory, temporary, etc.
|
11
10
|
#
|
12
11
|
module BenchmarkOptionParser
|
13
|
-
BANNER = "Usage: ruby #{$0} [options]\nSee ruby #{$0} -h for more options."
|
12
|
+
BANNER = "Usage: ruby #{$0} [options]\nSee ruby #{$0} -h for more options.".freeze
|
14
13
|
|
15
14
|
def self.print_banner
|
16
15
|
puts BANNER
|
@@ -38,8 +37,11 @@ module BenchmarkOptionParser
|
|
38
37
|
end
|
39
38
|
end
|
40
39
|
|
40
|
+
OptionsStruct = Struct.new( :adapter, :table_types, :delete_on_finish, :number_of_objects, :outputs,
|
41
|
+
:benchmark_all_types, keyword_init: true )
|
42
|
+
OutputStruct = Struct.new( :format, :filename, keyword_init: true )
|
41
43
|
def self.parse( args )
|
42
|
-
options =
|
44
|
+
options = OptionsStruct.new(
|
43
45
|
adapter: 'mysql2',
|
44
46
|
table_types: {},
|
45
47
|
delete_on_finish: true,
|
@@ -81,12 +83,12 @@ module BenchmarkOptionParser
|
|
81
83
|
|
82
84
|
# print results in CSV format
|
83
85
|
opts.on( "--to-csv [String]", "Print results in a CSV file format" ) do |filename|
|
84
|
-
options.outputs <<
|
86
|
+
options.outputs << OutputStruct.new( format: 'csv', filename: filename)
|
85
87
|
end
|
86
88
|
|
87
89
|
# print results in HTML format
|
88
90
|
opts.on( "--to-html [String]", "Print results in HTML format" ) do |filename|
|
89
|
-
options.outputs <<
|
91
|
+
options.outputs << OutputStruct.new( format: 'html', filename: filename )
|
90
92
|
end
|
91
93
|
end # end opt.parse!
|
92
94
|
|
@@ -100,7 +102,7 @@ module BenchmarkOptionParser
|
|
100
102
|
end
|
101
103
|
|
102
104
|
options.number_of_objects = [1000] if options.number_of_objects.empty?
|
103
|
-
options.outputs = [
|
105
|
+
options.outputs = [OutputStruct.new( format: 'html', filename: 'benchmark.html')] if options.outputs.empty?
|
104
106
|
|
105
107
|
print_options( options )
|
106
108
|
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "ostruct"
|
4
|
-
|
5
3
|
module ActiveRecord::Import::ConnectionAdapters; end
|
6
4
|
|
7
5
|
module ActiveRecord::Import # :nodoc:
|
@@ -94,7 +92,7 @@ module ActiveRecord::Import # :nodoc:
|
|
94
92
|
env = ActiveSupport::Callbacks::Filters::Environment.new(model, false, nil)
|
95
93
|
if runner.respond_to?(:call) # ActiveRecord < 5.1
|
96
94
|
runner.call(env)
|
97
|
-
else # ActiveRecord 5.1
|
95
|
+
else # ActiveRecord >= 5.1
|
98
96
|
# Note that this is a gross simplification of ActiveSupport::Callbacks#run_callbacks.
|
99
97
|
# It's technically possible for there to exist an "around" callback in the
|
100
98
|
# :validate chain, but this would be an aberration, since Rails doesn't define
|
@@ -107,7 +105,8 @@ module ActiveRecord::Import # :nodoc:
|
|
107
105
|
# no real-world use case for it.
|
108
106
|
raise "The :validate callback chain contains an 'around' callback, which is unsupported" unless runner.final?
|
109
107
|
runner.invoke_before(env)
|
110
|
-
|
108
|
+
# Ensure a truthy value is returned. ActiveRecord < 7.2 always returned an array.
|
109
|
+
runner.invoke_after(env) || []
|
111
110
|
end
|
112
111
|
elsif @validate_callbacks.method(:compile).arity == 0 # ActiveRecord = 4.0
|
113
112
|
model.instance_eval @validate_callbacks.compile
|
@@ -858,12 +857,11 @@ class ActiveRecord::Base
|
|
858
857
|
|
859
858
|
private
|
860
859
|
|
861
|
-
def associated_options(options,
|
860
|
+
def associated_options(options, association)
|
862
861
|
return options unless options.key?(:recursive_on_duplicate_key_update)
|
863
862
|
|
864
|
-
table_name = associated_class.arel_table.name.to_sym
|
865
863
|
options.merge(
|
866
|
-
on_duplicate_key_update: options[:recursive_on_duplicate_key_update][
|
864
|
+
on_duplicate_key_update: options[:recursive_on_duplicate_key_update][association]
|
867
865
|
)
|
868
866
|
end
|
869
867
|
|
@@ -972,12 +970,12 @@ class ActiveRecord::Base
|
|
972
970
|
options.delete(:returning)
|
973
971
|
|
974
972
|
associated_objects_by_class.each_value do |associations|
|
975
|
-
associations.
|
973
|
+
associations.each do |association, associated_records|
|
976
974
|
next if associated_records.empty?
|
977
975
|
|
978
976
|
associated_class = associated_records.first.class
|
979
977
|
associated_class.bulk_import(associated_records,
|
980
|
-
associated_options(options,
|
978
|
+
associated_options(options, association))
|
981
979
|
end
|
982
980
|
end
|
983
981
|
end
|
data/test/github/database.yml
CHANGED
@@ -3,7 +3,7 @@ common: &common
|
|
3
3
|
password: root
|
4
4
|
encoding: utf8
|
5
5
|
collation: utf8_general_ci
|
6
|
-
host:
|
6
|
+
host: 127.0.0.1
|
7
7
|
database: activerecord_import_test
|
8
8
|
|
9
9
|
jdbcpostgresql: &postgresql
|
@@ -54,7 +54,7 @@ seamless_database_pool:
|
|
54
54
|
pool_adapter: mysql2
|
55
55
|
prepared_statements: false
|
56
56
|
master:
|
57
|
-
host:
|
57
|
+
host: 127.0.0.1
|
58
58
|
|
59
59
|
sqlite:
|
60
60
|
adapter: sqlite
|
data/test/import_test.rb
CHANGED
@@ -741,14 +741,8 @@ describe "#import" do
|
|
741
741
|
]
|
742
742
|
Book.import books
|
743
743
|
assert_equal 2, Book.count
|
744
|
-
|
745
|
-
|
746
|
-
assert_equal 'draft', Book.first.read_attribute('status')
|
747
|
-
assert_equal 'published', Book.last.read_attribute('status')
|
748
|
-
else
|
749
|
-
assert_equal 0, Book.first.read_attribute('status')
|
750
|
-
assert_equal 1, Book.last.read_attribute('status')
|
751
|
-
end
|
744
|
+
assert_equal 'draft', Book.first.read_attribute('status')
|
745
|
+
assert_equal 'published', Book.last.read_attribute('status')
|
752
746
|
end
|
753
747
|
|
754
748
|
it 'should be able to import enum fields with default value' do
|
@@ -758,32 +752,19 @@ describe "#import" do
|
|
758
752
|
]
|
759
753
|
Book.import books
|
760
754
|
assert_equal 1, Book.count
|
761
|
-
|
762
|
-
if ENV['AR_VERSION'].to_i >= 5.0
|
763
|
-
assert_equal 'draft', Book.first.read_attribute('status')
|
764
|
-
else
|
765
|
-
assert_equal 0, Book.first.read_attribute('status')
|
766
|
-
end
|
755
|
+
assert_equal 'draft', Book.first.read_attribute('status')
|
767
756
|
end
|
768
757
|
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
if ENV['AR_VERSION'].to_i >= 5.0
|
780
|
-
assert_equal 'draft', Book.first.read_attribute('status')
|
781
|
-
assert_equal 'published', Book.last.read_attribute('status')
|
782
|
-
else
|
783
|
-
assert_equal 0, Book.first.read_attribute('status')
|
784
|
-
assert_equal 1, Book.last.read_attribute('status')
|
785
|
-
end
|
786
|
-
end
|
758
|
+
it 'should be able to import enum fields by name' do
|
759
|
+
Book.delete_all if Book.count > 0
|
760
|
+
books = [
|
761
|
+
Book.new(author_name: "Foo", title: "Baz", status: :draft),
|
762
|
+
Book.new(author_name: "Foo2", title: "Baz2", status: :published),
|
763
|
+
]
|
764
|
+
Book.import books
|
765
|
+
assert_equal 2, Book.count
|
766
|
+
assert_equal 'draft', Book.first.read_attribute('status')
|
767
|
+
assert_equal 'published', Book.last.read_attribute('status')
|
787
768
|
end
|
788
769
|
end
|
789
770
|
|
@@ -796,13 +777,8 @@ describe "#import" do
|
|
796
777
|
Book.import columns, values
|
797
778
|
assert_equal 2, Book.count
|
798
779
|
|
799
|
-
|
800
|
-
|
801
|
-
assert_equal 'published', Book.last.read_attribute('status')
|
802
|
-
else
|
803
|
-
assert_equal 0, Book.first.read_attribute('status')
|
804
|
-
assert_equal 1, Book.last.read_attribute('status')
|
805
|
-
end
|
780
|
+
assert_equal 'draft', Book.first.read_attribute('status')
|
781
|
+
assert_equal 'published', Book.last.read_attribute('status')
|
806
782
|
end
|
807
783
|
end
|
808
784
|
|
data/test/models/author.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Author < ActiveRecord::Base
|
4
|
-
if ENV['AR_VERSION'].to_f >=
|
4
|
+
if ENV['AR_VERSION'].to_f >= 8.0
|
5
|
+
has_many :composite_books, foreign_key: [:id, :author_id], inverse_of: :author
|
6
|
+
elsif ENV['AR_VERSION'].to_f >= 7.1
|
5
7
|
has_many :composite_books, query_constraints: [:id, :author_id], inverse_of: :author
|
6
8
|
end
|
7
9
|
end
|
data/test/models/book.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
class Book < ActiveRecord::Base
|
4
4
|
belongs_to :topic, inverse_of: :books
|
5
|
-
if ENV['AR_VERSION'].to_f <= 7.0
|
5
|
+
if ENV['AR_VERSION'].to_f <= 7.0 || ENV['AR_VERSION'].to_f >= 8.0
|
6
6
|
belongs_to :tag, foreign_key: [:tag_id, :parent_id] unless ENV["SKIP_COMPOSITE_PK"]
|
7
7
|
else
|
8
8
|
belongs_to :tag, query_constraints: [:tag_id, :parent_id] unless ENV["SKIP_COMPOSITE_PK"]
|
@@ -10,5 +10,9 @@ class Book < ActiveRecord::Base
|
|
10
10
|
has_many :chapters, inverse_of: :book
|
11
11
|
has_many :discounts, as: :discountable
|
12
12
|
has_many :end_notes, inverse_of: :book
|
13
|
-
|
13
|
+
if ENV['AR_VERSION'].to_f >= 8.0
|
14
|
+
enum :status, [:draft, :published]
|
15
|
+
else
|
16
|
+
enum status: [:draft, :published]
|
17
|
+
end
|
14
18
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
class CompositeBook < ActiveRecord::Base
|
4
4
|
self.primary_key = %i[id author_id]
|
5
5
|
belongs_to :author
|
6
|
-
if ENV['AR_VERSION'].to_f <= 7.0
|
6
|
+
if ENV['AR_VERSION'].to_f <= 7.0 || ENV['AR_VERSION'].to_f >= 8.0
|
7
7
|
unless ENV["SKIP_COMPOSITE_PK"]
|
8
8
|
has_many :composite_chapters, inverse_of: :composite_book,
|
9
9
|
foreign_key: [:id, :author_id]
|
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class CompositeChapter < ActiveRecord::Base
|
4
|
-
if ENV['AR_VERSION'].to_f >=
|
4
|
+
if ENV['AR_VERSION'].to_f >= 8.0
|
5
|
+
belongs_to :composite_book, inverse_of: :composite_chapters,
|
6
|
+
foreign_key: [:composite_book_id, :author_id]
|
7
|
+
elsif ENV['AR_VERSION'].to_f >= 7.1
|
5
8
|
belongs_to :composite_book, inverse_of: :composite_chapters,
|
6
9
|
query_constraints: [:composite_book_id, :author_id]
|
7
10
|
end
|
data/test/models/customer.rb
CHANGED
data/test/models/order.rb
CHANGED
data/test/models/tag_alias.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
class TagAlias < ActiveRecord::Base
|
4
4
|
unless ENV["SKIP_COMPOSITE_PK"]
|
5
|
-
if ENV['AR_VERSION'].to_f <= 7.0
|
5
|
+
if ENV['AR_VERSION'].to_f <= 7.0 || ENV['AR_VERSION'].to_f >= 8.0
|
6
6
|
belongs_to :tag, foreign_key: [:tag_id, :parent_id], required: true
|
7
7
|
else
|
8
8
|
belongs_to :tag, query_constraints: [:tag_id, :parent_id], required: true
|
data/test/models/topic.rb
CHANGED
@@ -16,6 +16,7 @@ class Topic < ActiveRecord::Base
|
|
16
16
|
before_validation -> { errors.add(:title, :invalid) if title == 'invalid' }
|
17
17
|
|
18
18
|
has_many :books, inverse_of: :topic
|
19
|
+
has_many :novels, inverse_of: :topic, class_name: "Book"
|
19
20
|
belongs_to :parent, class_name: "Topic"
|
20
21
|
|
21
22
|
composed_of :description, mapping: [%w(title title), %w(author_name author_name)], allow_nil: true, class_name: "TopicDescription"
|
@@ -3,11 +3,7 @@
|
|
3
3
|
class ActiveSupport::TestCase
|
4
4
|
include ActiveRecord::TestFixtures
|
5
5
|
|
6
|
-
|
7
|
-
self.use_transactional_tests = true
|
8
|
-
else
|
9
|
-
self.use_transactional_fixtures = true
|
10
|
-
end
|
6
|
+
self.use_transactional_tests = true
|
11
7
|
|
12
8
|
class << self
|
13
9
|
def requires_active_record_version(version_string, &blk)
|
@@ -84,14 +84,12 @@ def should_support_mysql_import_functionality
|
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
Book.import books
|
94
|
-
end
|
87
|
+
context "with virtual columns" do
|
88
|
+
let(:books) { [Book.new(author_name: "foo", title: "bar")] }
|
89
|
+
|
90
|
+
it "ignores virtual columns and creates record" do
|
91
|
+
assert_difference "Book.count", +1 do
|
92
|
+
Book.import books
|
95
93
|
end
|
96
94
|
end
|
97
95
|
end
|
@@ -38,10 +38,8 @@ def should_support_postgresql_import_functionality
|
|
38
38
|
assert !topic.changed?
|
39
39
|
end
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
assert topic.previous_changes.present?
|
44
|
-
end
|
41
|
+
it "moves the dirty changes to previous_changes" do
|
42
|
+
assert topic.previous_changes.present?
|
45
43
|
end
|
46
44
|
|
47
45
|
it "marks models as persisted" do
|
@@ -96,15 +94,9 @@ def should_support_postgresql_import_functionality
|
|
96
94
|
describe "returning" do
|
97
95
|
let(:books) { [Book.new(author_name: "King", title: "It")] }
|
98
96
|
let(:result) { Book.import(books, returning: %w(author_name title)) }
|
99
|
-
let(:book_id)
|
100
|
-
|
101
|
-
|
102
|
-
else
|
103
|
-
books.first.id.to_s
|
104
|
-
end
|
105
|
-
end
|
106
|
-
let(:true_returning_value) { ENV['AR_VERSION'].to_f >= 5.0 ? true : 't' }
|
107
|
-
let(:false_returning_value) { ENV['AR_VERSION'].to_f >= 5.0 ? false : 'f' }
|
97
|
+
let(:book_id) { books.first.id }
|
98
|
+
let(:true_returning_value) { true }
|
99
|
+
let(:false_returning_value) { false }
|
108
100
|
|
109
101
|
it "creates records" do
|
110
102
|
assert_difference("Book.count", +1) { result }
|
@@ -222,23 +214,21 @@ def should_support_postgresql_import_functionality
|
|
222
214
|
end
|
223
215
|
end
|
224
216
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
let(:vendors) { [vendor] }
|
217
|
+
describe "with a uuid primary key" do
|
218
|
+
let(:vendor) { Vendor.new(name: "foo") }
|
219
|
+
let(:vendors) { [vendor] }
|
229
220
|
|
230
|
-
|
231
|
-
|
232
|
-
Vendor.import vendors
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
|
-
it "assigns an id to the model objects" do
|
221
|
+
it "creates records" do
|
222
|
+
assert_difference "Vendor.count", +1 do
|
237
223
|
Vendor.import vendors
|
238
|
-
assert_not_nil vendor.id
|
239
224
|
end
|
240
225
|
end
|
241
226
|
|
227
|
+
it "assigns an id to the model objects" do
|
228
|
+
Vendor.import vendors
|
229
|
+
assert_not_nil vendor.id
|
230
|
+
end
|
231
|
+
|
242
232
|
describe "with an assigned uuid primary key" do
|
243
233
|
let(:id) { SecureRandom.uuid }
|
244
234
|
let(:vendor) { Vendor.new(id: id, name: "foo") }
|
@@ -254,44 +244,38 @@ def should_support_postgresql_import_functionality
|
|
254
244
|
end
|
255
245
|
|
256
246
|
describe "with store accessor fields" do
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
Vendor.import vendors
|
262
|
-
end
|
263
|
-
assert_equal(100, Vendor.first.size)
|
247
|
+
it "imports values for json fields" do
|
248
|
+
vendors = [Vendor.new(name: 'Vendor 1', size: 100)]
|
249
|
+
assert_difference "Vendor.count", +1 do
|
250
|
+
Vendor.import vendors
|
264
251
|
end
|
252
|
+
assert_equal(100, Vendor.first.size)
|
253
|
+
end
|
265
254
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
end
|
271
|
-
assert_equal('John Smith', Vendor.first.contact)
|
255
|
+
it "imports values for hstore fields" do
|
256
|
+
vendors = [Vendor.new(name: 'Vendor 1', contact: 'John Smith')]
|
257
|
+
assert_difference "Vendor.count", +1 do
|
258
|
+
Vendor.import vendors
|
272
259
|
end
|
260
|
+
assert_equal('John Smith', Vendor.first.contact)
|
273
261
|
end
|
274
262
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
Vendor.import vendors
|
280
|
-
end
|
281
|
-
assert_equal('12345', Vendor.first.charge_code)
|
263
|
+
it "imports values for jsonb fields" do
|
264
|
+
vendors = [Vendor.new(name: 'Vendor 1', charge_code: '12345')]
|
265
|
+
assert_difference "Vendor.count", +1 do
|
266
|
+
Vendor.import vendors
|
282
267
|
end
|
268
|
+
assert_equal('12345', Vendor.first.charge_code)
|
283
269
|
end
|
284
270
|
end
|
285
271
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
Vendor.import vendors
|
292
|
-
end
|
293
|
-
assert_equal({}, Vendor.first.json_data)
|
272
|
+
describe "with serializable fields" do
|
273
|
+
it "imports default values as correct data type" do
|
274
|
+
vendors = [Vendor.new(name: 'Vendor 1')]
|
275
|
+
assert_difference "Vendor.count", +1 do
|
276
|
+
Vendor.import vendors
|
294
277
|
end
|
278
|
+
assert_equal({}, Vendor.first.json_data)
|
295
279
|
end
|
296
280
|
|
297
281
|
%w(json jsonb).each do |json_type|
|
@@ -278,6 +278,45 @@ def should_support_recursive_import
|
|
278
278
|
end
|
279
279
|
assert_equal new_chapter_title, Chapter.find(example_chapter.id).title
|
280
280
|
end
|
281
|
+
|
282
|
+
context "when a non-standard association name is used" do
|
283
|
+
let(:new_topics) do
|
284
|
+
topic = Build(:topic)
|
285
|
+
|
286
|
+
2.times do
|
287
|
+
novel = topic.novels.build(title: FactoryBot.generate(:book_title), author_name: 'Stephen King')
|
288
|
+
3.times do
|
289
|
+
novel.chapters.build(title: FactoryBot.generate(:chapter_title))
|
290
|
+
end
|
291
|
+
|
292
|
+
4.times do
|
293
|
+
novel.end_notes.build(note: FactoryBot.generate(:end_note))
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
[topic]
|
298
|
+
end
|
299
|
+
|
300
|
+
it "updates nested associated objects" do
|
301
|
+
new_chapter_title = 'The Final Chapter'
|
302
|
+
novel = new_topics.first.novels.first
|
303
|
+
novel.author_name = 'Richard Bachman'
|
304
|
+
|
305
|
+
example_chapter = novel.chapters.first
|
306
|
+
example_chapter.title = new_chapter_title
|
307
|
+
|
308
|
+
assert_nothing_raised do
|
309
|
+
Topic.import new_topics,
|
310
|
+
recursive: true,
|
311
|
+
on_duplicate_key_update: [:id],
|
312
|
+
recursive_on_duplicate_key_update: {
|
313
|
+
novels: { conflict_target: [:id], columns: [:author_name] },
|
314
|
+
chapters: { conflict_target: [:id], columns: [:title] }
|
315
|
+
}
|
316
|
+
end
|
317
|
+
assert_equal new_chapter_title, Chapter.find(example_chapter.id).title
|
318
|
+
end
|
319
|
+
end
|
281
320
|
end
|
282
321
|
end
|
283
322
|
|
data/test/test_helper.rb
CHANGED
@@ -13,19 +13,16 @@ ENV["RAILS_ENV"] = "test"
|
|
13
13
|
require "bundler"
|
14
14
|
Bundler.setup
|
15
15
|
|
16
|
-
|
16
|
+
unless RbConfig::CONFIG["RUBY_INSTALL_NAME"] =~ /jruby/
|
17
|
+
require 'pry'
|
18
|
+
require 'pry-byebug'
|
19
|
+
end
|
17
20
|
|
18
21
|
require "active_record"
|
19
22
|
require "active_record/fixtures"
|
20
23
|
require "active_support/test_case"
|
21
|
-
|
22
|
-
|
23
|
-
require 'test/unit'
|
24
|
-
require 'mocha/test_unit'
|
25
|
-
else
|
26
|
-
require 'active_support/testing/autorun'
|
27
|
-
require "mocha/minitest"
|
28
|
-
end
|
24
|
+
require 'active_support/testing/autorun'
|
25
|
+
require "mocha/minitest"
|
29
26
|
|
30
27
|
require 'timecop'
|
31
28
|
require 'chronic'
|
@@ -38,16 +35,6 @@ rescue LoadError
|
|
38
35
|
end
|
39
36
|
end
|
40
37
|
|
41
|
-
# Support MySQL 5.7
|
42
|
-
if ActiveSupport::VERSION::STRING < "4.1"
|
43
|
-
require "active_record/connection_adapters/mysql2_adapter"
|
44
|
-
class ActiveRecord::ConnectionAdapters::Mysql2Adapter
|
45
|
-
NATIVE_DATABASE_TYPES[:primary_key] = "int(11) auto_increment PRIMARY KEY"
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
require "ruby-debug" if RUBY_VERSION.to_f < 1.9
|
50
|
-
|
51
38
|
adapter = ENV["ARE_DB"] || "sqlite3"
|
52
39
|
|
53
40
|
FileUtils.mkdir_p 'log'
|
@@ -99,4 +86,4 @@ Dir["#{File.dirname(__FILE__)}/models/*.rb"].sort.each { |file| require file }
|
|
99
86
|
# Prevent this deprecation warning from breaking the tests.
|
100
87
|
Rake::FileList.send(:remove_method, :import)
|
101
88
|
|
102
|
-
ActiveSupport::TestCase.test_order = :random
|
89
|
+
ActiveSupport::TestCase.test_order = :random
|
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:
|
4
|
+
version: 2.0.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: 2024-
|
11
|
+
date: 2024-12-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -70,15 +70,13 @@ files:
|
|
70
70
|
- benchmarks/models/test_myisam.rb
|
71
71
|
- benchmarks/schema/mysql2_schema.rb
|
72
72
|
- docker-compose.yml
|
73
|
-
- gemfiles/4.2.gemfile
|
74
|
-
- gemfiles/5.0.gemfile
|
75
|
-
- gemfiles/5.1.gemfile
|
76
73
|
- gemfiles/5.2.gemfile
|
77
74
|
- gemfiles/6.0.gemfile
|
78
75
|
- gemfiles/6.1.gemfile
|
79
76
|
- gemfiles/7.0.gemfile
|
80
77
|
- gemfiles/7.1.gemfile
|
81
78
|
- gemfiles/7.2.gemfile
|
79
|
+
- gemfiles/8.0.gemfile
|
82
80
|
- lib/activerecord-import.rb
|
83
81
|
- lib/activerecord-import/active_record/adapters/abstract_adapter.rb
|
84
82
|
- lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb
|
@@ -98,9 +96,6 @@ files:
|
|
98
96
|
- lib/activerecord-import/adapters/trilogy_adapter.rb
|
99
97
|
- lib/activerecord-import/base.rb
|
100
98
|
- lib/activerecord-import/import.rb
|
101
|
-
- lib/activerecord-import/mysql2.rb
|
102
|
-
- lib/activerecord-import/postgresql.rb
|
103
|
-
- lib/activerecord-import/sqlite3.rb
|
104
99
|
- lib/activerecord-import/synchronize.rb
|
105
100
|
- lib/activerecord-import/value_sets_parser.rb
|
106
101
|
- lib/activerecord-import/version.rb
|
@@ -187,7 +182,7 @@ licenses:
|
|
187
182
|
- MIT
|
188
183
|
metadata:
|
189
184
|
changelog_uri: https://github.com/zdennis/activerecord-import/blob/master/CHANGELOG.md
|
190
|
-
post_install_message:
|
185
|
+
post_install_message:
|
191
186
|
rdoc_options: []
|
192
187
|
require_paths:
|
193
188
|
- lib
|
@@ -202,8 +197,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
202
197
|
- !ruby/object:Gem::Version
|
203
198
|
version: '0'
|
204
199
|
requirements: []
|
205
|
-
rubygems_version: 3.
|
206
|
-
signing_key:
|
200
|
+
rubygems_version: 3.0.3.1
|
201
|
+
signing_key:
|
207
202
|
specification_version: 4
|
208
203
|
summary: Bulk insert extension for ActiveRecord
|
209
204
|
test_files:
|
data/gemfiles/4.2.gemfile
DELETED
data/gemfiles/5.0.gemfile
DELETED
data/gemfiles/5.1.gemfile
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
warn <<-MSG
|
4
|
-
[DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
|
5
|
-
is deprecated. Update to autorequire using 'require "activerecord-import"'. See
|
6
|
-
http://github.com/zdennis/activerecord-import/wiki/Requiring for more information
|
7
|
-
MSG
|
8
|
-
|
9
|
-
require "activerecord-import"
|
@@ -1,9 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
warn <<-MSG
|
4
|
-
[DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
|
5
|
-
is deprecated. Update to autorequire using 'require "activerecord-import"'. See
|
6
|
-
http://github.com/zdennis/activerecord-import/wiki/Requiring for more information
|
7
|
-
MSG
|
8
|
-
|
9
|
-
require "activerecord-import"
|
@@ -1,9 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
warn <<-MSG
|
4
|
-
[DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
|
5
|
-
is deprecated. Update to autorequire using 'require "activerecord-import"'. See
|
6
|
-
http://github.com/zdennis/activerecord-import/wiki/Requiring for more information
|
7
|
-
MSG
|
8
|
-
|
9
|
-
require "activerecord-import"
|