activerecord-import 1.1.0 → 1.4.1
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 +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
|
|