activerecord-import 1.1.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/test.yaml +107 -0
- data/.rubocop.yml +74 -8
- data/Brewfile +3 -1
- data/CHANGELOG.md +38 -3
- data/Gemfile +5 -7
- data/README.markdown +13 -12
- data/Rakefile +2 -0
- data/activerecord-import.gemspec +4 -3
- data/benchmarks/benchmark.rb +7 -1
- data/benchmarks/lib/base.rb +2 -0
- data/benchmarks/lib/cli_parser.rb +3 -1
- data/benchmarks/lib/float.rb +2 -0
- data/benchmarks/lib/mysql2_benchmark.rb +2 -0
- data/benchmarks/lib/output_to_csv.rb +2 -0
- data/benchmarks/lib/output_to_html.rb +4 -2
- data/benchmarks/models/test_innodb.rb +2 -0
- data/benchmarks/models/test_memory.rb +2 -0
- data/benchmarks/models/test_myisam.rb +2 -0
- data/benchmarks/schema/mysql2_schema.rb +2 -0
- data/gemfiles/4.2.gemfile +2 -0
- data/gemfiles/5.0.gemfile +2 -0
- data/gemfiles/5.1.gemfile +2 -0
- data/gemfiles/5.2.gemfile +2 -0
- data/gemfiles/6.0.gemfile +2 -0
- data/gemfiles/6.1.gemfile +3 -0
- data/gemfiles/7.0.gemfile +4 -0
- data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +6 -4
- data/lib/activerecord-import/active_record/adapters/jdbcpostgresql_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +2 -0
- data/lib/activerecord-import/adapters/abstract_adapter.rb +2 -0
- data/lib/activerecord-import/adapters/em_mysql2_adapter.rb +2 -0
- data/lib/activerecord-import/adapters/mysql2_adapter.rb +2 -0
- data/lib/activerecord-import/adapters/mysql_adapter.rb +3 -1
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +41 -30
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +8 -8
- data/lib/activerecord-import/base.rb +3 -1
- data/lib/activerecord-import/import.rb +62 -32
- data/lib/activerecord-import/mysql2.rb +2 -0
- data/lib/activerecord-import/postgresql.rb +2 -0
- data/lib/activerecord-import/sqlite3.rb +2 -0
- data/lib/activerecord-import/synchronize.rb +3 -1
- data/lib/activerecord-import/value_sets_parser.rb +2 -0
- data/lib/activerecord-import/version.rb +3 -1
- data/lib/activerecord-import.rb +3 -1
- data/test/adapters/jdbcmysql.rb +2 -0
- data/test/adapters/jdbcpostgresql.rb +2 -0
- data/test/adapters/jdbcsqlite3.rb +2 -0
- data/test/adapters/makara_postgis.rb +2 -0
- data/test/adapters/mysql2.rb +2 -0
- data/test/adapters/mysql2_makara.rb +2 -0
- data/test/adapters/mysql2spatial.rb +2 -0
- data/test/adapters/postgis.rb +2 -0
- data/test/adapters/postgresql.rb +2 -0
- data/test/adapters/postgresql_makara.rb +2 -0
- data/test/adapters/seamless_database_pool.rb +2 -0
- data/test/adapters/spatialite.rb +2 -0
- data/test/adapters/sqlite3.rb +2 -0
- data/test/{travis → github}/database.yml +3 -1
- data/test/import_test.rb +45 -2
- data/test/jdbcmysql/import_test.rb +2 -0
- data/test/jdbcpostgresql/import_test.rb +2 -0
- data/test/jdbcsqlite3/import_test.rb +2 -0
- data/test/makara_postgis/import_test.rb +2 -0
- data/test/models/account.rb +2 -0
- data/test/models/alarm.rb +2 -0
- data/test/models/animal.rb +2 -0
- data/test/models/bike_maker.rb +2 -0
- data/test/models/book.rb +2 -0
- data/test/models/car.rb +2 -0
- data/test/models/card.rb +5 -0
- data/test/models/chapter.rb +2 -0
- data/test/models/customer.rb +8 -0
- data/test/models/deck.rb +8 -0
- data/test/models/dictionary.rb +2 -0
- data/test/models/discount.rb +2 -0
- data/test/models/end_note.rb +2 -0
- data/test/models/group.rb +2 -0
- data/test/models/order.rb +8 -0
- data/test/models/playing_card.rb +4 -0
- data/test/models/promotion.rb +2 -0
- data/test/models/question.rb +2 -0
- data/test/models/rule.rb +2 -0
- data/test/models/tag.rb +3 -0
- data/test/models/tag_alias.rb +5 -0
- data/test/models/topic.rb +2 -0
- data/test/models/user.rb +2 -0
- data/test/models/user_token.rb +2 -0
- data/test/models/vendor.rb +2 -0
- data/test/models/widget.rb +2 -0
- data/test/mysql2/import_test.rb +2 -0
- data/test/mysql2_makara/import_test.rb +2 -0
- data/test/mysqlspatial2/import_test.rb +2 -0
- data/test/postgis/import_test.rb +2 -0
- data/test/postgresql/import_test.rb +2 -0
- data/test/schema/generic_schema.rb +33 -0
- data/test/schema/jdbcpostgresql_schema.rb +2 -0
- data/test/schema/mysql2_schema.rb +2 -0
- data/test/schema/postgis_schema.rb +2 -0
- data/test/schema/postgresql_schema.rb +2 -0
- data/test/schema/sqlite3_schema.rb +2 -0
- data/test/schema/version.rb +2 -0
- data/test/sqlite3/import_test.rb +2 -0
- data/test/support/active_support/test_case_extensions.rb +2 -0
- data/test/support/assertions.rb +2 -0
- data/test/support/factories.rb +2 -0
- data/test/support/generate.rb +4 -2
- data/test/support/mysql/import_examples.rb +2 -1
- data/test/support/postgresql/import_examples.rb +65 -2
- data/test/support/shared_examples/on_duplicate_key_ignore.rb +2 -0
- data/test/support/shared_examples/on_duplicate_key_update.rb +2 -0
- data/test/support/shared_examples/recursive_import.rb +23 -1
- data/test/support/sqlite3/import_examples.rb +2 -1
- data/test/synchronize_test.rb +2 -0
- data/test/test_helper.rb +19 -2
- data/test/value_sets_bytes_parser_test.rb +2 -0
- data/test/value_sets_records_parser_test.rb +2 -0
- metadata +25 -15
- data/.travis.yml +0 -76
- data/gemfiles/3.2.gemfile +0 -2
- data/gemfiles/4.0.gemfile +0 -2
- data/gemfiles/4.1.gemfile +0 -2
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord::Import::MysqlAdapter
|
|
2
4
|
include ActiveRecord::Import::ImportSupport
|
|
3
5
|
include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
|
|
@@ -82,7 +84,7 @@ module ActiveRecord::Import::MysqlAdapter
|
|
|
82
84
|
# Returns a generated ON DUPLICATE KEY UPDATE statement given the passed
|
|
83
85
|
# in +args+.
|
|
84
86
|
def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
|
|
85
|
-
sql = ' ON DUPLICATE KEY UPDATE '
|
|
87
|
+
sql = ' ON DUPLICATE KEY UPDATE '.dup
|
|
86
88
|
arg = args.first
|
|
87
89
|
locking_column = args.last
|
|
88
90
|
if arg.is_a?( Array )
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord::Import::PostgreSQLAdapter
|
|
2
4
|
include ActiveRecord::Import::ImportSupport
|
|
3
5
|
include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
|
|
@@ -6,7 +8,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
|
6
8
|
|
|
7
9
|
def insert_many( sql, values, options = {}, *args ) # :nodoc:
|
|
8
10
|
number_of_inserts = 1
|
|
9
|
-
returned_values =
|
|
11
|
+
returned_values = {}
|
|
10
12
|
ids = []
|
|
11
13
|
results = []
|
|
12
14
|
|
|
@@ -18,47 +20,53 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
|
18
20
|
|
|
19
21
|
sql2insert = base_sql + values.join( ',' ) + post_sql
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
if
|
|
23
|
+
selections = returning_selections(options)
|
|
24
|
+
if selections.blank? || (options[:no_returning] && !options[:recursive])
|
|
23
25
|
insert( sql2insert, *args )
|
|
24
26
|
else
|
|
25
|
-
returned_values = if
|
|
27
|
+
returned_values = if selections.size > 1
|
|
26
28
|
# Select composite columns
|
|
27
|
-
|
|
29
|
+
db_result = select_all( sql2insert, *args )
|
|
30
|
+
{ values: db_result.rows, columns: db_result.columns }
|
|
28
31
|
else
|
|
29
|
-
select_values( sql2insert, *args )
|
|
32
|
+
{ values: select_values( sql2insert, *args ) }
|
|
30
33
|
end
|
|
31
34
|
clear_query_cache if query_cache_enabled
|
|
32
35
|
end
|
|
33
36
|
|
|
34
37
|
if options[:returning].blank?
|
|
35
|
-
ids = returned_values
|
|
38
|
+
ids = Array(returned_values[:values])
|
|
36
39
|
elsif options[:primary_key].blank?
|
|
37
|
-
|
|
40
|
+
options[:returning_columns] ||= returned_values[:columns]
|
|
41
|
+
results = Array(returned_values[:values])
|
|
38
42
|
else
|
|
39
43
|
# split primary key and returning columns
|
|
40
|
-
ids, results = split_ids_and_results(returned_values,
|
|
44
|
+
ids, results, options[:returning_columns] = split_ids_and_results(returned_values, options)
|
|
41
45
|
end
|
|
42
46
|
|
|
43
47
|
ActiveRecord::Import::Result.new([], number_of_inserts, ids, results)
|
|
44
48
|
end
|
|
45
49
|
|
|
46
|
-
def split_ids_and_results(
|
|
50
|
+
def split_ids_and_results( selections, options )
|
|
47
51
|
ids = []
|
|
48
|
-
|
|
52
|
+
returning_values = []
|
|
53
|
+
|
|
54
|
+
columns = Array(selections[:columns])
|
|
55
|
+
values = Array(selections[:values])
|
|
49
56
|
id_indexes = Array(options[:primary_key]).map { |key| columns.index(key) }
|
|
50
|
-
|
|
57
|
+
returning_columns = columns.reject.with_index { |_, index| id_indexes.include?(index) }
|
|
58
|
+
returning_indexes = returning_columns.map { |column| columns.index(column) }
|
|
51
59
|
|
|
52
60
|
values.each do |value|
|
|
53
61
|
value_array = Array(value)
|
|
54
|
-
ids << id_indexes.map { |
|
|
55
|
-
|
|
62
|
+
ids << id_indexes.map { |index| value_array[index] }
|
|
63
|
+
returning_values << returning_indexes.map { |index| value_array[index] }
|
|
56
64
|
end
|
|
57
65
|
|
|
58
66
|
ids.map!(&:first) if id_indexes.size == 1
|
|
59
|
-
|
|
67
|
+
returning_values.map!(&:first) if returning_columns.size == 1
|
|
60
68
|
|
|
61
|
-
[ids,
|
|
69
|
+
[ids, returning_values, returning_columns]
|
|
62
70
|
end
|
|
63
71
|
|
|
64
72
|
def next_value_for_sequence(sequence_name)
|
|
@@ -79,19 +87,24 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
|
79
87
|
|
|
80
88
|
sql += super(table_name, options)
|
|
81
89
|
|
|
82
|
-
|
|
83
|
-
unless
|
|
84
|
-
sql << " RETURNING
|
|
90
|
+
selections = returning_selections(options)
|
|
91
|
+
unless selections.blank? || (options[:no_returning] && !options[:recursive])
|
|
92
|
+
sql << " RETURNING #{selections.join(', ')}"
|
|
85
93
|
end
|
|
86
94
|
|
|
87
95
|
sql
|
|
88
96
|
end
|
|
89
97
|
|
|
90
|
-
def
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
98
|
+
def returning_selections(options)
|
|
99
|
+
selections = []
|
|
100
|
+
column_names = Array(options[:model].column_names)
|
|
101
|
+
|
|
102
|
+
selections += Array(options[:primary_key]) if options[:primary_key].present?
|
|
103
|
+
selections += Array(options[:returning]) if options[:returning].present?
|
|
104
|
+
|
|
105
|
+
selections.map do |selection|
|
|
106
|
+
column_names.include?(selection.to_s) ? "\"#{selection}\"" : selection
|
|
107
|
+
end
|
|
95
108
|
end
|
|
96
109
|
|
|
97
110
|
# Add a column to be updated on duplicate key update
|
|
@@ -123,7 +136,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
|
123
136
|
arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
|
|
124
137
|
return unless arg.is_a?( Hash )
|
|
125
138
|
|
|
126
|
-
sql = ' ON CONFLICT '
|
|
139
|
+
sql = ' ON CONFLICT '.dup
|
|
127
140
|
conflict_target = sql_for_conflict_target( arg )
|
|
128
141
|
|
|
129
142
|
columns = arg.fetch( :columns, [] )
|
|
@@ -179,9 +192,9 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
|
179
192
|
if constraint_name.present?
|
|
180
193
|
"ON CONSTRAINT #{constraint_name} "
|
|
181
194
|
elsif conflict_target.present?
|
|
182
|
-
'('
|
|
183
|
-
|
|
184
|
-
|
|
195
|
+
sql = '(' + Array( conflict_target ).reject( &:blank? ).join( ', ' ) + ') '
|
|
196
|
+
sql += "WHERE #{index_predicate} " if index_predicate
|
|
197
|
+
sql
|
|
185
198
|
end
|
|
186
199
|
end
|
|
187
200
|
|
|
@@ -203,8 +216,6 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
|
203
216
|
true
|
|
204
217
|
end
|
|
205
218
|
|
|
206
|
-
private
|
|
207
|
-
|
|
208
219
|
def database_version
|
|
209
220
|
defined?(postgresql_version) ? postgresql_version : super
|
|
210
221
|
end
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord::Import::SQLite3Adapter
|
|
2
4
|
include ActiveRecord::Import::ImportSupport
|
|
3
5
|
include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
|
|
4
6
|
|
|
5
|
-
MIN_VERSION_FOR_IMPORT = "3.7.11"
|
|
6
|
-
MIN_VERSION_FOR_UPSERT = "3.24.0"
|
|
7
|
+
MIN_VERSION_FOR_IMPORT = "3.7.11"
|
|
8
|
+
MIN_VERSION_FOR_UPSERT = "3.24.0"
|
|
7
9
|
SQLITE_LIMIT_COMPOUND_SELECT = 500
|
|
8
10
|
|
|
9
11
|
# Override our conformance to ActiveRecord::Import::ImportSupport interface
|
|
@@ -97,7 +99,7 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
|
97
99
|
arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
|
|
98
100
|
return unless arg.is_a?( Hash )
|
|
99
101
|
|
|
100
|
-
sql = ' ON CONFLICT '
|
|
102
|
+
sql = ' ON CONFLICT '.dup
|
|
101
103
|
conflict_target = sql_for_conflict_target( arg )
|
|
102
104
|
|
|
103
105
|
columns = arg.fetch( :columns, [] )
|
|
@@ -150,9 +152,9 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
|
150
152
|
conflict_target = args[:conflict_target]
|
|
151
153
|
index_predicate = args[:index_predicate]
|
|
152
154
|
if conflict_target.present?
|
|
153
|
-
'('
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
sql = '(' + Array( conflict_target ).reject( &:blank? ).join( ', ' ) + ') '
|
|
156
|
+
sql += "WHERE #{index_predicate} " if index_predicate
|
|
157
|
+
sql
|
|
156
158
|
end
|
|
157
159
|
end
|
|
158
160
|
|
|
@@ -166,8 +168,6 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
|
166
168
|
exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('duplicate key')
|
|
167
169
|
end
|
|
168
170
|
|
|
169
|
-
private
|
|
170
|
-
|
|
171
171
|
def database_version
|
|
172
172
|
defined?(sqlite_version) ? sqlite_version : super
|
|
173
173
|
end
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "pathname"
|
|
2
4
|
require "active_record"
|
|
3
5
|
require "active_record/version"
|
|
4
6
|
|
|
5
7
|
module ActiveRecord::Import
|
|
6
|
-
ADAPTER_PATH = "activerecord-import/active_record/adapters"
|
|
8
|
+
ADAPTER_PATH = "activerecord-import/active_record/adapters"
|
|
7
9
|
|
|
8
10
|
def self.base_adapter(adapter)
|
|
9
11
|
case adapter
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "ostruct"
|
|
2
4
|
|
|
3
5
|
module ActiveRecord::Import::ConnectionAdapters; end
|
|
@@ -34,7 +36,7 @@ module ActiveRecord::Import #:nodoc:
|
|
|
34
36
|
@validate_callbacks = klass._validate_callbacks.dup
|
|
35
37
|
|
|
36
38
|
@validate_callbacks.each_with_index do |callback, i|
|
|
37
|
-
filter = callback.raw_filter
|
|
39
|
+
filter = callback.respond_to?(:raw_filter) ? callback.raw_filter : callback.filter
|
|
38
40
|
next unless filter.class.name =~ /Validations::PresenceValidator/ ||
|
|
39
41
|
(!@options[:validate_uniqueness] &&
|
|
40
42
|
filter.is_a?(ActiveRecord::Validations::UniquenessValidator))
|
|
@@ -49,13 +51,13 @@ module ActiveRecord::Import #:nodoc:
|
|
|
49
51
|
associations = klass.reflect_on_all_associations(:belongs_to)
|
|
50
52
|
associations.each do |assoc|
|
|
51
53
|
if (index = attrs.index(assoc.name))
|
|
52
|
-
key = assoc.foreign_key.to_sym
|
|
54
|
+
key = assoc.foreign_key.is_a?(Array) ? assoc.foreign_key.map(&:to_sym) : assoc.foreign_key.to_sym
|
|
53
55
|
attrs[index] = key unless attrs.include?(key)
|
|
54
56
|
end
|
|
55
57
|
end
|
|
56
58
|
end
|
|
57
59
|
|
|
58
|
-
filter.instance_variable_set(:@attributes, attrs)
|
|
60
|
+
filter.instance_variable_set(:@attributes, attrs.flatten)
|
|
59
61
|
|
|
60
62
|
if @validate_callbacks.respond_to?(:chain, true)
|
|
61
63
|
@validate_callbacks.send(:chain).tap do |chain|
|
|
@@ -547,7 +549,7 @@ class ActiveRecord::Base
|
|
|
547
549
|
alias import! bulk_import! unless ActiveRecord::Base.respond_to? :import!
|
|
548
550
|
|
|
549
551
|
def import_helper( *args )
|
|
550
|
-
options = { validate: true, timestamps: true, track_validation_failures: false }
|
|
552
|
+
options = { model: self, validate: true, timestamps: true, track_validation_failures: false }
|
|
551
553
|
options.merge!( args.pop ) if args.last.is_a? Hash
|
|
552
554
|
# making sure that current model's primary key is used
|
|
553
555
|
options[:primary_key] = primary_key
|
|
@@ -734,7 +736,10 @@ class ActiveRecord::Base
|
|
|
734
736
|
set_attributes_and_mark_clean(models, return_obj, timestamps, options)
|
|
735
737
|
|
|
736
738
|
# if there are auto-save associations on the models we imported that are new, import them as well
|
|
737
|
-
|
|
739
|
+
if options[:recursive]
|
|
740
|
+
options[:on_duplicate_key_update] = on_duplicate_key_update unless on_duplicate_key_update.nil?
|
|
741
|
+
import_associations(models, options.dup.merge(validate: false))
|
|
742
|
+
end
|
|
738
743
|
end
|
|
739
744
|
|
|
740
745
|
return_obj
|
|
@@ -775,21 +780,22 @@ class ActiveRecord::Base
|
|
|
775
780
|
unless scope_columns.blank?
|
|
776
781
|
scope_columns.zip(scope_values).each do |name, value|
|
|
777
782
|
name_as_sym = name.to_sym
|
|
778
|
-
next if column_names.include?(name_as_sym)
|
|
779
|
-
|
|
780
|
-
is_sti = (name_as_sym == inheritance_column.to_sym && self < base_class)
|
|
781
|
-
value = Array(value).first if is_sti
|
|
782
|
-
|
|
783
|
+
next if column_names.include?(name_as_sym) || name_as_sym == inheritance_column.to_sym
|
|
783
784
|
column_names << name_as_sym
|
|
784
785
|
array_of_attributes.each { |attrs| attrs << value }
|
|
785
786
|
end
|
|
786
787
|
end
|
|
787
788
|
|
|
789
|
+
if finder_needs_type_condition?
|
|
790
|
+
unless column_names.include?(inheritance_column.to_sym)
|
|
791
|
+
column_names << inheritance_column.to_sym
|
|
792
|
+
array_of_attributes.each { |attrs| attrs << sti_name }
|
|
793
|
+
end
|
|
794
|
+
end
|
|
795
|
+
|
|
788
796
|
columns = column_names.each_with_index.map do |name, i|
|
|
789
797
|
column = columns_hash[name.to_s]
|
|
790
|
-
|
|
791
798
|
raise ActiveRecord::Import::MissingColumnError.new(name.to_s, i) if column.nil?
|
|
792
|
-
|
|
793
799
|
column
|
|
794
800
|
end
|
|
795
801
|
|
|
@@ -853,7 +859,7 @@ class ActiveRecord::Base
|
|
|
853
859
|
model.id = id
|
|
854
860
|
|
|
855
861
|
timestamps.each do |attr, value|
|
|
856
|
-
model.send(attr + "=", value)
|
|
862
|
+
model.send(attr + "=", value) if model.send(attr).nil?
|
|
857
863
|
end
|
|
858
864
|
end
|
|
859
865
|
end
|
|
@@ -871,19 +877,28 @@ class ActiveRecord::Base
|
|
|
871
877
|
end
|
|
872
878
|
end
|
|
873
879
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
880
|
+
set_value = lambda do |model, column, value|
|
|
881
|
+
val = deserialize_value.call(column, value)
|
|
882
|
+
if model.attribute_names.include?(column)
|
|
883
|
+
model.send("#{column}=", val)
|
|
884
|
+
else
|
|
885
|
+
attributes = attributes_builder.build_from_database(model.attributes.merge(column => val))
|
|
886
|
+
model.instance_variable_set(:@attributes, attributes)
|
|
887
|
+
end
|
|
888
|
+
end
|
|
889
|
+
|
|
890
|
+
columns = Array(options[:returning_columns])
|
|
891
|
+
results = Array(import_result.results)
|
|
892
|
+
if models.size == results.size
|
|
893
|
+
single_column = columns.first if columns.size == 1
|
|
894
|
+
results.each_with_index do |result, index|
|
|
878
895
|
model = models[index]
|
|
879
896
|
|
|
880
897
|
if single_column
|
|
881
|
-
|
|
882
|
-
model.send(single_column, val)
|
|
898
|
+
set_value.call(model, single_column, result)
|
|
883
899
|
else
|
|
884
900
|
columns.each_with_index do |column, col_index|
|
|
885
|
-
|
|
886
|
-
model.send("#{column}=", val)
|
|
901
|
+
set_value.call(model, column, result[col_index])
|
|
887
902
|
end
|
|
888
903
|
end
|
|
889
904
|
end
|
|
@@ -907,15 +922,19 @@ class ActiveRecord::Base
|
|
|
907
922
|
changed_columns = model.changed
|
|
908
923
|
association_reflections = model.class.reflect_on_all_associations(:belongs_to)
|
|
909
924
|
association_reflections.each do |association_reflection|
|
|
910
|
-
column_name = association_reflection.foreign_key
|
|
911
925
|
next if association_reflection.options[:polymorphic]
|
|
912
|
-
next if changed_columns.include?(column_name)
|
|
913
|
-
association = model.association(association_reflection.name)
|
|
914
|
-
association = association.target
|
|
915
|
-
next if association.blank? || model.public_send(column_name).present?
|
|
916
926
|
|
|
917
|
-
|
|
918
|
-
|
|
927
|
+
column_names = Array(association_reflection.foreign_key).map(&:to_s)
|
|
928
|
+
column_names.each_with_index do |column_name, column_index|
|
|
929
|
+
next if changed_columns.include?(column_name)
|
|
930
|
+
|
|
931
|
+
association = model.association(association_reflection.name)
|
|
932
|
+
association = association.target
|
|
933
|
+
next if association.blank? || model.public_send(column_name).present?
|
|
934
|
+
|
|
935
|
+
association_primary_key = Array(association_reflection.association_primary_key)[column_index]
|
|
936
|
+
model.public_send("#{column_name}=", association.send(association_primary_key))
|
|
937
|
+
end
|
|
919
938
|
end
|
|
920
939
|
end
|
|
921
940
|
|
|
@@ -928,8 +947,9 @@ class ActiveRecord::Base
|
|
|
928
947
|
associated_objects_by_class = {}
|
|
929
948
|
models.each { |model| find_associated_objects_for_import(associated_objects_by_class, model) }
|
|
930
949
|
|
|
931
|
-
# :on_duplicate_key_update
|
|
932
|
-
options.delete(:on_duplicate_key_update)
|
|
950
|
+
# :on_duplicate_key_update only supported for all fields
|
|
951
|
+
options.delete(:on_duplicate_key_update) unless options[:on_duplicate_key_update] == :all
|
|
952
|
+
# :returning not supported for associations
|
|
933
953
|
options.delete(:returning)
|
|
934
954
|
|
|
935
955
|
associated_objects_by_class.each_value do |associations|
|
|
@@ -961,8 +981,13 @@ class ActiveRecord::Base
|
|
|
961
981
|
changed_objects.each do |child|
|
|
962
982
|
child.public_send("#{association_reflection.foreign_key}=", model.id)
|
|
963
983
|
# For polymorphic associations
|
|
984
|
+
association_name = if model.class.respond_to?(:polymorphic_name)
|
|
985
|
+
model.class.polymorphic_name
|
|
986
|
+
else
|
|
987
|
+
model.class.base_class
|
|
988
|
+
end
|
|
964
989
|
association_reflection.type.try do |type|
|
|
965
|
-
child.public_send("#{type}=",
|
|
990
|
+
child.public_send("#{type}=", association_name)
|
|
966
991
|
end
|
|
967
992
|
end
|
|
968
993
|
associated_objects_by_class[model.class.name][association_reflection.name].concat changed_objects
|
|
@@ -1023,7 +1048,12 @@ class ActiveRecord::Base
|
|
|
1023
1048
|
end
|
|
1024
1049
|
|
|
1025
1050
|
# use tz as set in ActiveRecord::Base
|
|
1026
|
-
|
|
1051
|
+
default_timezone = if ActiveRecord.respond_to?(:default_timezone)
|
|
1052
|
+
ActiveRecord.default_timezone
|
|
1053
|
+
else
|
|
1054
|
+
ActiveRecord::Base.default_timezone
|
|
1055
|
+
end
|
|
1056
|
+
timestamp = default_timezone == :utc ? Time.now.utc : Time.now
|
|
1027
1057
|
|
|
1028
1058
|
[:create, :update].each do |action|
|
|
1029
1059
|
timestamp_columns[action].each do |column|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord # :nodoc:
|
|
2
4
|
class Base # :nodoc:
|
|
3
5
|
# Synchronizes the passed in ActiveRecord instances with data
|
|
@@ -39,7 +41,7 @@ module ActiveRecord # :nodoc:
|
|
|
39
41
|
|
|
40
42
|
next unless matched_instance
|
|
41
43
|
|
|
42
|
-
instance.
|
|
44
|
+
instance.instance_variable_set :@association_cache, {}
|
|
43
45
|
instance.send :clear_aggregation_cache if instance.respond_to?(:clear_aggregation_cache, true)
|
|
44
46
|
instance.instance_variable_set :@attributes, matched_instance.instance_variable_get(:@attributes)
|
|
45
47
|
|
data/lib/activerecord-import.rb
CHANGED
data/test/adapters/jdbcmysql.rb
CHANGED
data/test/adapters/mysql2.rb
CHANGED
data/test/adapters/postgis.rb
CHANGED
data/test/adapters/postgresql.rb
CHANGED
data/test/adapters/spatialite.rb
CHANGED
data/test/adapters/sqlite3.rb
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
common: &common
|
|
2
2
|
username: root
|
|
3
|
-
password:
|
|
3
|
+
password: root
|
|
4
4
|
encoding: utf8
|
|
5
|
+
collation: utf8_general_ci
|
|
5
6
|
host: localhost
|
|
6
7
|
database: activerecord_import_test
|
|
7
8
|
|
|
@@ -37,6 +38,7 @@ oracle:
|
|
|
37
38
|
postgresql: &postgresql
|
|
38
39
|
<<: *common
|
|
39
40
|
username: postgres
|
|
41
|
+
password: postgres
|
|
40
42
|
adapter: postgresql
|
|
41
43
|
min_messages: warning
|
|
42
44
|
|
data/test/import_test.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require File.expand_path('../test_helper', __FILE__)
|
|
2
4
|
|
|
3
5
|
describe "#import" do
|
|
@@ -159,6 +161,25 @@ describe "#import" do
|
|
|
159
161
|
Tag.import columns, values, validate: false
|
|
160
162
|
end
|
|
161
163
|
end
|
|
164
|
+
|
|
165
|
+
it "should import models that are required to belong to models with composite primary keys" do
|
|
166
|
+
tag = Tag.create!(tag_id: 1, publisher_id: 1, tag: 'Mystery')
|
|
167
|
+
valid_tag_alias = TagAlias.new(tag_id: tag.tag_id, parent_id: tag.publisher_id, alias: 'Detective')
|
|
168
|
+
invalid_tag_aliases = [
|
|
169
|
+
TagAlias.new(tag_id: nil, parent_id: nil, alias: 'Detective'),
|
|
170
|
+
TagAlias.new(tag_id: tag.tag_id, parent_id: nil, alias: 'Detective'),
|
|
171
|
+
TagAlias.new(tag_id: nil, parent_id: tag.publisher_id, alias: 'Detective'),
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
assert_difference "TagAlias.count", +1 do
|
|
175
|
+
TagAlias.import [valid_tag_alias]
|
|
176
|
+
end
|
|
177
|
+
invalid_tag_aliases.each do |invalid_tag_alias|
|
|
178
|
+
assert_no_difference "TagAlias.count" do
|
|
179
|
+
TagAlias.import [invalid_tag_alias]
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
162
183
|
end
|
|
163
184
|
end
|
|
164
185
|
|
|
@@ -169,7 +190,17 @@ describe "#import" do
|
|
|
169
190
|
assert_difference "Dictionary.count", +1 do
|
|
170
191
|
Dictionary.import dictionaries
|
|
171
192
|
end
|
|
172
|
-
assert_equal "Dictionary", Dictionary.
|
|
193
|
+
assert_equal "Dictionary", Dictionary.last.type
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it "should import arrays successfully" do
|
|
197
|
+
columns = [:author_name, :title]
|
|
198
|
+
values = [["Noah Webster", "Webster's Dictionary"]]
|
|
199
|
+
|
|
200
|
+
assert_difference "Dictionary.count", +1 do
|
|
201
|
+
Dictionary.import columns, values
|
|
202
|
+
end
|
|
203
|
+
assert_equal "Dictionary", Dictionary.last.type
|
|
173
204
|
end
|
|
174
205
|
end
|
|
175
206
|
|
|
@@ -545,7 +576,11 @@ describe "#import" do
|
|
|
545
576
|
context "when the timestamps columns are present" do
|
|
546
577
|
setup do
|
|
547
578
|
@existing_book = Book.create(title: "Fell", author_name: "Curry", publisher: "Bayer", created_at: 2.years.ago.utc, created_on: 2.years.ago.utc, updated_at: 2.years.ago.utc, updated_on: 2.years.ago.utc)
|
|
548
|
-
ActiveRecord
|
|
579
|
+
if ActiveRecord.respond_to?(:default_timezone)
|
|
580
|
+
ActiveRecord.default_timezone = :utc
|
|
581
|
+
else
|
|
582
|
+
ActiveRecord::Base.default_timezone = :utc
|
|
583
|
+
end
|
|
549
584
|
Timecop.freeze(time) do
|
|
550
585
|
assert_difference "Book.count", +2 do
|
|
551
586
|
Book.import %w(title author_name publisher created_at created_on updated_at updated_on), [["LDAP", "Big Bird", "Del Rey", nil, nil, nil, nil], [@existing_book.title, @existing_book.author_name, @existing_book.publisher, @existing_book.created_at, @existing_book.created_on, @existing_book.updated_at, @existing_book.updated_on]]
|
|
@@ -661,6 +696,14 @@ describe "#import" do
|
|
|
661
696
|
assert_equal [val1, val2], scope.map(&column).sort
|
|
662
697
|
end
|
|
663
698
|
|
|
699
|
+
context "for cards and decks" do
|
|
700
|
+
it "works when the polymorphic name is different than base class name" do
|
|
701
|
+
deck = Deck.create(id: 1, name: 'test')
|
|
702
|
+
deck.cards.import [:id, :deck_type], [[1, 'PlayingCard']]
|
|
703
|
+
assert_equal deck.cards.first.deck_type, "PlayingCard"
|
|
704
|
+
end
|
|
705
|
+
end
|
|
706
|
+
|
|
664
707
|
it "works importing array of hashes" do
|
|
665
708
|
scope.import [{ column => val1 }, { column => val2 }]
|
|
666
709
|
|