activerecord-import 0.22.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 +5 -5
- data/.github/workflows/test.yaml +107 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +74 -8
- data/Brewfile +3 -1
- data/CHANGELOG.md +235 -3
- data/Gemfile +22 -15
- data/LICENSE +21 -56
- data/README.markdown +574 -22
- data/Rakefile +4 -1
- data/activerecord-import.gemspec +6 -5
- 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/{mysql_schema.rb → 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 +4 -0
- data/gemfiles/6.0.gemfile +4 -0
- data/gemfiles/6.1.gemfile +4 -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 +10 -2
- 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 +19 -11
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +56 -37
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +128 -9
- data/lib/activerecord-import/base.rb +12 -2
- data/lib/activerecord-import/import.rb +300 -136
- 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 +4 -2
- data/lib/activerecord-import/value_sets_parser.rb +4 -0
- data/lib/activerecord-import/version.rb +3 -1
- data/lib/activerecord-import.rb +4 -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 +3 -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 +159 -8
- 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 +10 -0
- data/test/models/account.rb +5 -0
- data/test/models/alarm.rb +2 -0
- data/test/models/animal.rb +8 -0
- data/test/models/bike_maker.rb +9 -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 +5 -0
- data/test/models/user_token.rb +6 -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 +53 -0
- data/test/schema/jdbcpostgresql_schema.rb +2 -0
- data/test/schema/mysql2_schema.rb +21 -0
- data/test/schema/postgis_schema.rb +2 -0
- data/test/schema/postgresql_schema.rb +18 -0
- data/test/schema/sqlite3_schema.rb +15 -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 +10 -8
- data/test/support/generate.rb +10 -8
- data/test/support/mysql/import_examples.rb +14 -1
- data/test/support/postgresql/import_examples.rb +140 -3
- data/test/support/shared_examples/on_duplicate_key_ignore.rb +2 -0
- data/test/support/shared_examples/on_duplicate_key_update.rb +263 -0
- data/test/support/shared_examples/recursive_import.rb +76 -4
- data/test/support/sqlite3/import_examples.rb +191 -26
- data/test/synchronize_test.rb +2 -0
- data/test/test_helper.rb +36 -3
- data/test/value_sets_bytes_parser_test.rb +2 -0
- data/test/value_sets_records_parser_test.rb +2 -0
- metadata +46 -18
- data/.travis.yml +0 -61
- data/gemfiles/3.2.gemfile +0 -2
- data/gemfiles/4.0.gemfile +0 -2
- data/gemfiles/4.1.gemfile +0 -2
- data/test/schema/mysql_schema.rb +0 -16
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "ostruct"
|
|
2
4
|
|
|
3
5
|
module ActiveRecord::Import::ConnectionAdapters; end
|
|
@@ -24,27 +26,66 @@ module ActiveRecord::Import #:nodoc:
|
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
class Validator
|
|
27
|
-
def initialize(options = {})
|
|
29
|
+
def initialize(klass, options = {})
|
|
28
30
|
@options = options
|
|
31
|
+
@validator_class = klass
|
|
32
|
+
init_validations(klass)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def init_validations(klass)
|
|
36
|
+
@validate_callbacks = klass._validate_callbacks.dup
|
|
37
|
+
|
|
38
|
+
@validate_callbacks.each_with_index do |callback, i|
|
|
39
|
+
filter = callback.respond_to?(:raw_filter) ? callback.raw_filter : callback.filter
|
|
40
|
+
next unless filter.class.name =~ /Validations::PresenceValidator/ ||
|
|
41
|
+
(!@options[:validate_uniqueness] &&
|
|
42
|
+
filter.is_a?(ActiveRecord::Validations::UniquenessValidator))
|
|
43
|
+
|
|
44
|
+
callback = callback.dup
|
|
45
|
+
filter = filter.dup
|
|
46
|
+
attrs = filter.instance_variable_get(:@attributes).dup
|
|
47
|
+
|
|
48
|
+
if filter.is_a?(ActiveRecord::Validations::UniquenessValidator)
|
|
49
|
+
attrs = []
|
|
50
|
+
else
|
|
51
|
+
associations = klass.reflect_on_all_associations(:belongs_to)
|
|
52
|
+
associations.each do |assoc|
|
|
53
|
+
if (index = attrs.index(assoc.name))
|
|
54
|
+
key = assoc.foreign_key.is_a?(Array) ? assoc.foreign_key.map(&:to_sym) : assoc.foreign_key.to_sym
|
|
55
|
+
attrs[index] = key unless attrs.include?(key)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
filter.instance_variable_set(:@attributes, attrs.flatten)
|
|
61
|
+
|
|
62
|
+
if @validate_callbacks.respond_to?(:chain, true)
|
|
63
|
+
@validate_callbacks.send(:chain).tap do |chain|
|
|
64
|
+
callback.instance_variable_set(:@filter, filter)
|
|
65
|
+
chain[i] = callback
|
|
66
|
+
end
|
|
67
|
+
else
|
|
68
|
+
callback.raw_filter = filter
|
|
69
|
+
callback.filter = callback.send(:_compile_filter, filter)
|
|
70
|
+
@validate_callbacks[i] = callback
|
|
71
|
+
end
|
|
72
|
+
end
|
|
29
73
|
end
|
|
30
74
|
|
|
31
75
|
def valid_model?(model)
|
|
76
|
+
init_validations(model.class) unless model.class == @validator_class
|
|
77
|
+
|
|
32
78
|
validation_context = @options[:validate_with_context]
|
|
33
79
|
validation_context ||= (model.new_record? ? :create : :update)
|
|
34
|
-
|
|
35
80
|
current_context = model.send(:validation_context)
|
|
81
|
+
|
|
36
82
|
begin
|
|
37
83
|
model.send(:validation_context=, validation_context)
|
|
38
84
|
model.errors.clear
|
|
39
85
|
|
|
40
|
-
validate_callbacks = model._validate_callbacks.dup
|
|
41
|
-
model._validate_callbacks.each do |callback|
|
|
42
|
-
validate_callbacks.delete(callback) if callback.raw_filter.is_a? ActiveRecord::Validations::UniquenessValidator
|
|
43
|
-
end
|
|
44
|
-
|
|
45
86
|
model.run_callbacks(:validation) do
|
|
46
87
|
if defined?(ActiveSupport::Callbacks::Filters::Environment) # ActiveRecord >= 4.1
|
|
47
|
-
runner = validate_callbacks.compile
|
|
88
|
+
runner = @validate_callbacks.compile
|
|
48
89
|
env = ActiveSupport::Callbacks::Filters::Environment.new(model, false, nil)
|
|
49
90
|
if runner.respond_to?(:call) # ActiveRecord < 5.1
|
|
50
91
|
runner.call(env)
|
|
@@ -63,10 +104,10 @@ module ActiveRecord::Import #:nodoc:
|
|
|
63
104
|
runner.invoke_before(env)
|
|
64
105
|
runner.invoke_after(env)
|
|
65
106
|
end
|
|
66
|
-
elsif validate_callbacks.method(:compile).arity == 0 # ActiveRecord = 4.0
|
|
67
|
-
model.instance_eval validate_callbacks.compile
|
|
107
|
+
elsif @validate_callbacks.method(:compile).arity == 0 # ActiveRecord = 4.0
|
|
108
|
+
model.instance_eval @validate_callbacks.compile
|
|
68
109
|
else # ActiveRecord 3.x
|
|
69
|
-
model.instance_eval validate_callbacks.compile(nil, model)
|
|
110
|
+
model.instance_eval @validate_callbacks.compile(nil, model)
|
|
70
111
|
end
|
|
71
112
|
end
|
|
72
113
|
|
|
@@ -79,13 +120,14 @@ module ActiveRecord::Import #:nodoc:
|
|
|
79
120
|
end
|
|
80
121
|
|
|
81
122
|
class ActiveRecord::Associations::CollectionProxy
|
|
82
|
-
def
|
|
83
|
-
@association.
|
|
123
|
+
def bulk_import(*args, &block)
|
|
124
|
+
@association.bulk_import(*args, &block)
|
|
84
125
|
end
|
|
126
|
+
alias import bulk_import unless respond_to? :import
|
|
85
127
|
end
|
|
86
128
|
|
|
87
129
|
class ActiveRecord::Associations::CollectionAssociation
|
|
88
|
-
def
|
|
130
|
+
def bulk_import(*args, &block)
|
|
89
131
|
unless owner.persisted?
|
|
90
132
|
raise ActiveRecord::RecordNotSaved, "You cannot call import unless the parent is saved"
|
|
91
133
|
end
|
|
@@ -94,9 +136,14 @@ class ActiveRecord::Associations::CollectionAssociation
|
|
|
94
136
|
|
|
95
137
|
model_klass = reflection.klass
|
|
96
138
|
symbolized_foreign_key = reflection.foreign_key.to_sym
|
|
97
|
-
symbolized_column_names = model_klass.column_names.map(&:to_sym)
|
|
98
139
|
|
|
99
|
-
|
|
140
|
+
symbolized_column_names = if model_klass.connection.respond_to?(:supports_virtual_columns?) && model_klass.connection.supports_virtual_columns?
|
|
141
|
+
model_klass.columns.reject(&:virtual?).map { |c| c.name.to_sym }
|
|
142
|
+
else
|
|
143
|
+
model_klass.column_names.map(&:to_sym)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
owner_primary_key = reflection.active_record_primary_key.to_sym
|
|
100
147
|
owner_primary_key_value = owner.send(owner_primary_key)
|
|
101
148
|
|
|
102
149
|
# assume array of model objects
|
|
@@ -118,7 +165,7 @@ class ActiveRecord::Associations::CollectionAssociation
|
|
|
118
165
|
m.public_send "#{reflection.type}=", owner.class.name if reflection.type
|
|
119
166
|
end
|
|
120
167
|
|
|
121
|
-
return model_klass.
|
|
168
|
+
return model_klass.bulk_import column_names, models, options
|
|
122
169
|
|
|
123
170
|
# supports array of hash objects
|
|
124
171
|
elsif args.last.is_a?( Array ) && args.last.first.is_a?(Hash)
|
|
@@ -157,7 +204,7 @@ class ActiveRecord::Associations::CollectionAssociation
|
|
|
157
204
|
end
|
|
158
205
|
end
|
|
159
206
|
|
|
160
|
-
return model_klass.
|
|
207
|
+
return model_klass.bulk_import column_names, array_of_attributes, options
|
|
161
208
|
|
|
162
209
|
# supports empty array
|
|
163
210
|
elsif args.last.is_a?( Array ) && args.last.empty?
|
|
@@ -192,23 +239,25 @@ class ActiveRecord::Associations::CollectionAssociation
|
|
|
192
239
|
end
|
|
193
240
|
end
|
|
194
241
|
|
|
195
|
-
return model_klass.
|
|
242
|
+
return model_klass.bulk_import column_names, array_of_attributes, options
|
|
196
243
|
else
|
|
197
244
|
raise ArgumentError, "Invalid arguments!"
|
|
198
245
|
end
|
|
199
246
|
end
|
|
247
|
+
alias import bulk_import unless respond_to? :import
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
module ActiveRecord::Import::Connection
|
|
251
|
+
def establish_connection(args = nil)
|
|
252
|
+
conn = super(args)
|
|
253
|
+
ActiveRecord::Import.load_from_connection_pool connection_pool
|
|
254
|
+
conn
|
|
255
|
+
end
|
|
200
256
|
end
|
|
201
257
|
|
|
202
258
|
class ActiveRecord::Base
|
|
203
259
|
class << self
|
|
204
|
-
|
|
205
|
-
conn = establish_connection_without_activerecord_import(*args)
|
|
206
|
-
ActiveRecord::Import.load_from_connection_pool connection_pool
|
|
207
|
-
conn
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
alias establish_connection_without_activerecord_import establish_connection
|
|
211
|
-
alias establish_connection establish_connection_with_activerecord_import
|
|
260
|
+
prepend ActiveRecord::Import::Connection
|
|
212
261
|
|
|
213
262
|
# Returns true if the current database connection adapter
|
|
214
263
|
# supports import functionality, otherwise returns false.
|
|
@@ -274,6 +323,9 @@ class ActiveRecord::Base
|
|
|
274
323
|
# == Options
|
|
275
324
|
# * +validate+ - true|false, tells import whether or not to use
|
|
276
325
|
# ActiveRecord validations. Validations are enforced by default.
|
|
326
|
+
# It skips the uniqueness validation for performance reasons.
|
|
327
|
+
# You can find more details here:
|
|
328
|
+
# https://github.com/zdennis/activerecord-import/issues/228
|
|
277
329
|
# * +ignore+ - true|false, an alias for on_duplicate_key_ignore.
|
|
278
330
|
# * +on_duplicate_key_ignore+ - true|false, tells import to discard
|
|
279
331
|
# records that contain duplicate keys. For Postgres 9.5+ it adds
|
|
@@ -282,8 +334,8 @@ class ActiveRecord::Base
|
|
|
282
334
|
# recursive import. For database adapters that normally support
|
|
283
335
|
# setting primary keys on imported objects, this option prevents
|
|
284
336
|
# that from occurring.
|
|
285
|
-
# * +on_duplicate_key_update+ - an Array or Hash, tells import to
|
|
286
|
-
# use MySQL's ON DUPLICATE KEY UPDATE or Postgres
|
|
337
|
+
# * +on_duplicate_key_update+ - :all, an Array, or Hash, tells import to
|
|
338
|
+
# use MySQL's ON DUPLICATE KEY UPDATE or Postgres/SQLite ON CONFLICT
|
|
287
339
|
# DO UPDATE ability. See On Duplicate Key Update below.
|
|
288
340
|
# * +synchronize+ - an array of ActiveRecord instances for the model
|
|
289
341
|
# that you are currently importing data into. This synchronizes
|
|
@@ -342,7 +394,15 @@ class ActiveRecord::Base
|
|
|
342
394
|
#
|
|
343
395
|
# == On Duplicate Key Update (MySQL)
|
|
344
396
|
#
|
|
345
|
-
# The :on_duplicate_key_update option can be either an Array or a Hash.
|
|
397
|
+
# The :on_duplicate_key_update option can be either :all, an Array, or a Hash.
|
|
398
|
+
#
|
|
399
|
+
# ==== Using :all
|
|
400
|
+
#
|
|
401
|
+
# The :on_duplicate_key_update option can be set to :all. All columns
|
|
402
|
+
# other than the primary key are updated. If a list of column names is
|
|
403
|
+
# supplied, only those columns will be updated. Below is an example:
|
|
404
|
+
#
|
|
405
|
+
# BlogPost.import columns, values, on_duplicate_key_update: :all
|
|
346
406
|
#
|
|
347
407
|
# ==== Using an Array
|
|
348
408
|
#
|
|
@@ -361,11 +421,19 @@ class ActiveRecord::Base
|
|
|
361
421
|
#
|
|
362
422
|
# BlogPost.import columns, attributes, on_duplicate_key_update: { title: :title }
|
|
363
423
|
#
|
|
364
|
-
# == On Duplicate Key Update (Postgres 9.5+)
|
|
424
|
+
# == On Duplicate Key Update (Postgres 9.5+ and SQLite 3.24+)
|
|
365
425
|
#
|
|
366
|
-
# The :on_duplicate_key_update option can be an Array or a Hash with up to
|
|
426
|
+
# The :on_duplicate_key_update option can be :all, an Array, or a Hash with up to
|
|
367
427
|
# three attributes, :conflict_target (and optionally :index_predicate) or
|
|
368
|
-
# :constraint_name, and :columns.
|
|
428
|
+
# :constraint_name (Postgres), and :columns.
|
|
429
|
+
#
|
|
430
|
+
# ==== Using :all
|
|
431
|
+
#
|
|
432
|
+
# The :on_duplicate_key_update option can be set to :all. All columns
|
|
433
|
+
# other than the primary key are updated. If a list of column names is
|
|
434
|
+
# supplied, only those columns will be updated. Below is an example:
|
|
435
|
+
#
|
|
436
|
+
# BlogPost.import columns, values, on_duplicate_key_update: :all
|
|
369
437
|
#
|
|
370
438
|
# ==== Using an Array
|
|
371
439
|
#
|
|
@@ -423,7 +491,15 @@ class ActiveRecord::Base
|
|
|
423
491
|
#
|
|
424
492
|
# ===== :columns
|
|
425
493
|
#
|
|
426
|
-
# The :columns attribute can be either an Array or a Hash.
|
|
494
|
+
# The :columns attribute can be either :all, an Array, or a Hash.
|
|
495
|
+
#
|
|
496
|
+
# ===== Using :all
|
|
497
|
+
#
|
|
498
|
+
# The :columns attribute can be :all. All columns other than the primary key will be updated.
|
|
499
|
+
# If a list of column names is supplied, only those columns will be updated.
|
|
500
|
+
# Below is an example:
|
|
501
|
+
#
|
|
502
|
+
# BlogPost.import columns, values, on_duplicate_key_update: { conflict_target: :slug, columns: :all }
|
|
427
503
|
#
|
|
428
504
|
# ===== Using an Array
|
|
429
505
|
#
|
|
@@ -447,7 +523,7 @@ class ActiveRecord::Base
|
|
|
447
523
|
# * num_inserts - the number of insert statements it took to import the data
|
|
448
524
|
# * ids - the primary keys of the imported ids if the adapter supports it, otherwise an empty array.
|
|
449
525
|
# * results - import results if the adapter supports it, otherwise an empty array.
|
|
450
|
-
def
|
|
526
|
+
def bulk_import(*args)
|
|
451
527
|
if args.first.is_a?( Array ) && args.first.first.is_a?(ActiveRecord::Base)
|
|
452
528
|
options = {}
|
|
453
529
|
options.merge!( args.pop ) if args.last.is_a?(Hash)
|
|
@@ -458,36 +534,29 @@ class ActiveRecord::Base
|
|
|
458
534
|
import_helper(*args)
|
|
459
535
|
end
|
|
460
536
|
end
|
|
537
|
+
alias import bulk_import unless ActiveRecord::Base.respond_to? :import
|
|
461
538
|
|
|
462
539
|
# Imports a collection of values if all values are valid. Import fails at the
|
|
463
540
|
# first encountered validation error and raises ActiveRecord::RecordInvalid
|
|
464
541
|
# with the failed instance.
|
|
465
|
-
def
|
|
542
|
+
def bulk_import!(*args)
|
|
466
543
|
options = args.last.is_a?( Hash ) ? args.pop : {}
|
|
467
544
|
options[:validate] = true
|
|
468
545
|
options[:raise_error] = true
|
|
469
546
|
|
|
470
|
-
|
|
547
|
+
bulk_import(*args, options)
|
|
471
548
|
end
|
|
549
|
+
alias import! bulk_import! unless ActiveRecord::Base.respond_to? :import!
|
|
472
550
|
|
|
473
551
|
def import_helper( *args )
|
|
474
|
-
options = { validate: true, timestamps: true }
|
|
552
|
+
options = { model: self, validate: true, timestamps: true, track_validation_failures: false }
|
|
475
553
|
options.merge!( args.pop ) if args.last.is_a? Hash
|
|
476
554
|
# making sure that current model's primary key is used
|
|
477
555
|
options[:primary_key] = primary_key
|
|
556
|
+
options[:locking_column] = locking_column if attribute_names.include?(locking_column)
|
|
478
557
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if on_duplicate_key_update && on_duplicate_key_update.duplicable?
|
|
482
|
-
options[:on_duplicate_key_update] = if on_duplicate_key_update.is_a?(Hash)
|
|
483
|
-
on_duplicate_key_update.each { |k, v| on_duplicate_key_update[k] = v.dup if v.duplicable? }
|
|
484
|
-
else
|
|
485
|
-
on_duplicate_key_update.dup
|
|
486
|
-
end
|
|
487
|
-
end
|
|
488
|
-
|
|
489
|
-
is_validating = options[:validate]
|
|
490
|
-
is_validating = true unless options[:validate_with_context].nil?
|
|
558
|
+
is_validating = options[:validate_with_context].present? ? true : options[:validate]
|
|
559
|
+
validator = ActiveRecord::Import::Validator.new(self, options)
|
|
491
560
|
|
|
492
561
|
# assume array of model objects
|
|
493
562
|
if args.last.is_a?( Array ) && args.last.first.is_a?(ActiveRecord::Base)
|
|
@@ -496,34 +565,48 @@ class ActiveRecord::Base
|
|
|
496
565
|
column_names = args.first.dup
|
|
497
566
|
else
|
|
498
567
|
models = args.first
|
|
499
|
-
column_names =
|
|
568
|
+
column_names = if connection.respond_to?(:supports_virtual_columns?) && connection.supports_virtual_columns?
|
|
569
|
+
columns.reject(&:virtual?).map(&:name)
|
|
570
|
+
else
|
|
571
|
+
self.column_names.dup
|
|
572
|
+
end
|
|
500
573
|
end
|
|
501
574
|
|
|
502
|
-
if models.first.id.nil?
|
|
503
|
-
|
|
575
|
+
if models.first.id.nil?
|
|
576
|
+
Array(primary_key).each do |c|
|
|
577
|
+
if column_names.include?(c) && columns_hash[c].type == :uuid
|
|
578
|
+
column_names.delete(c)
|
|
579
|
+
end
|
|
580
|
+
end
|
|
504
581
|
end
|
|
505
582
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
serialized_attributes
|
|
583
|
+
update_attrs = if record_timestamps && options[:timestamps]
|
|
584
|
+
if respond_to?(:timestamp_attributes_for_update, true)
|
|
585
|
+
send(:timestamp_attributes_for_update).map(&:to_sym)
|
|
586
|
+
else
|
|
587
|
+
allocate.send(:timestamp_attributes_for_update_in_model)
|
|
588
|
+
end
|
|
513
589
|
end
|
|
514
590
|
|
|
515
|
-
array_of_attributes =
|
|
591
|
+
array_of_attributes = []
|
|
592
|
+
|
|
593
|
+
models.each do |model|
|
|
516
594
|
if supports_setting_primary_key_of_imported_objects?
|
|
517
595
|
load_association_ids(model)
|
|
518
596
|
end
|
|
519
597
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
598
|
+
if is_validating && !validator.valid_model?(model)
|
|
599
|
+
raise(ActiveRecord::RecordInvalid, model) if options[:raise_error]
|
|
600
|
+
next
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
array_of_attributes << column_names.map do |name|
|
|
604
|
+
if model.persisted? &&
|
|
605
|
+
update_attrs && update_attrs.include?(name.to_sym) &&
|
|
606
|
+
!model.send("#{name}_changed?")
|
|
607
|
+
nil
|
|
525
608
|
else
|
|
526
|
-
model.
|
|
609
|
+
model.read_attribute(name.to_s)
|
|
527
610
|
end
|
|
528
611
|
end
|
|
529
612
|
end
|
|
@@ -550,7 +633,7 @@ class ActiveRecord::Base
|
|
|
550
633
|
end
|
|
551
634
|
# supports empty array
|
|
552
635
|
elsif args.last.is_a?( Array ) && args.last.empty?
|
|
553
|
-
return ActiveRecord::Import::Result.new([], 0, [])
|
|
636
|
+
return ActiveRecord::Import::Result.new([], 0, [], [])
|
|
554
637
|
# supports 2-element array and array
|
|
555
638
|
elsif args.size == 2 && args.first.is_a?( Array ) && args.last.is_a?( Array )
|
|
556
639
|
|
|
@@ -581,32 +664,69 @@ class ActiveRecord::Base
|
|
|
581
664
|
array_of_attributes.each { |a| a.concat(new_fields) }
|
|
582
665
|
end
|
|
583
666
|
|
|
667
|
+
# Don't modify incoming arguments
|
|
668
|
+
on_duplicate_key_update = options[:on_duplicate_key_update]
|
|
669
|
+
if on_duplicate_key_update
|
|
670
|
+
updatable_columns = symbolized_column_names.reject { |c| symbolized_primary_key.include? c }
|
|
671
|
+
options[:on_duplicate_key_update] = if on_duplicate_key_update.is_a?(Hash)
|
|
672
|
+
on_duplicate_key_update.each_with_object({}) do |(k, v), duped_options|
|
|
673
|
+
duped_options[k] = if k == :columns && v == :all
|
|
674
|
+
updatable_columns
|
|
675
|
+
elsif v.duplicable?
|
|
676
|
+
v.dup
|
|
677
|
+
else
|
|
678
|
+
v
|
|
679
|
+
end
|
|
680
|
+
end
|
|
681
|
+
elsif on_duplicate_key_update == :all
|
|
682
|
+
updatable_columns
|
|
683
|
+
elsif on_duplicate_key_update.duplicable?
|
|
684
|
+
on_duplicate_key_update.dup
|
|
685
|
+
else
|
|
686
|
+
on_duplicate_key_update
|
|
687
|
+
end
|
|
688
|
+
end
|
|
689
|
+
|
|
584
690
|
timestamps = {}
|
|
585
691
|
|
|
586
692
|
# record timestamps unless disabled in ActiveRecord::Base
|
|
587
|
-
if record_timestamps && options
|
|
693
|
+
if record_timestamps && options[:timestamps]
|
|
588
694
|
timestamps = add_special_rails_stamps column_names, array_of_attributes, options
|
|
589
695
|
end
|
|
590
696
|
|
|
591
697
|
return_obj = if is_validating
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
models.
|
|
595
|
-
|
|
698
|
+
import_with_validations( column_names, array_of_attributes, options ) do |failed_instances|
|
|
699
|
+
if models
|
|
700
|
+
models.each { |m| failed_instances << m if m.errors.any? }
|
|
701
|
+
else
|
|
702
|
+
# create instances for each of our column/value sets
|
|
703
|
+
arr = validations_array_for_column_names_and_attributes( column_names, array_of_attributes )
|
|
704
|
+
|
|
705
|
+
# keep track of the instance and the position it is currently at. if this fails
|
|
706
|
+
# validation we'll use the index to remove it from the array_of_attributes
|
|
707
|
+
arr.each_with_index do |hsh, i|
|
|
708
|
+
# utilize block initializer syntax to prevent failure when 'mass_assignment_sanitizer = :strict'
|
|
709
|
+
model = new do |m|
|
|
710
|
+
hsh.each_pair { |k, v| m[k] = v }
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
next if validator.valid_model?(model)
|
|
596
714
|
raise(ActiveRecord::RecordInvalid, model) if options[:raise_error]
|
|
715
|
+
|
|
597
716
|
array_of_attributes[i] = nil
|
|
598
|
-
|
|
717
|
+
failure = model.dup
|
|
718
|
+
failure.errors.send(:initialize_dup, model.errors)
|
|
719
|
+
failed_instances << (options[:track_validation_failures] ? [i, failure] : failure )
|
|
599
720
|
end
|
|
721
|
+
array_of_attributes.compact!
|
|
600
722
|
end
|
|
601
|
-
else
|
|
602
|
-
import_with_validations( column_names, array_of_attributes, options )
|
|
603
723
|
end
|
|
604
724
|
else
|
|
605
725
|
import_without_validations_or_callbacks( column_names, array_of_attributes, options )
|
|
606
726
|
end
|
|
607
727
|
|
|
608
728
|
if options[:synchronize]
|
|
609
|
-
sync_keys = options[:synchronize_keys] ||
|
|
729
|
+
sync_keys = options[:synchronize_keys] || Array(primary_key)
|
|
610
730
|
synchronize( options[:synchronize], sync_keys)
|
|
611
731
|
end
|
|
612
732
|
return_obj.num_inserts = 0 if return_obj.num_inserts.nil?
|
|
@@ -616,7 +736,10 @@ class ActiveRecord::Base
|
|
|
616
736
|
set_attributes_and_mark_clean(models, return_obj, timestamps, options)
|
|
617
737
|
|
|
618
738
|
# if there are auto-save associations on the models we imported that are new, import them as well
|
|
619
|
-
|
|
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
|
|
620
743
|
end
|
|
621
744
|
|
|
622
745
|
return_obj
|
|
@@ -632,31 +755,9 @@ class ActiveRecord::Base
|
|
|
632
755
|
def import_with_validations( column_names, array_of_attributes, options = {} )
|
|
633
756
|
failed_instances = []
|
|
634
757
|
|
|
635
|
-
|
|
758
|
+
yield failed_instances if block_given?
|
|
636
759
|
|
|
637
|
-
if
|
|
638
|
-
yield validator, failed_instances
|
|
639
|
-
else
|
|
640
|
-
# create instances for each of our column/value sets
|
|
641
|
-
arr = validations_array_for_column_names_and_attributes( column_names, array_of_attributes )
|
|
642
|
-
|
|
643
|
-
# keep track of the instance and the position it is currently at. if this fails
|
|
644
|
-
# validation we'll use the index to remove it from the array_of_attributes
|
|
645
|
-
arr.each_with_index do |hsh, i|
|
|
646
|
-
model = new
|
|
647
|
-
hsh.each_pair { |k, v| model[k] = v }
|
|
648
|
-
next if validator.valid_model? model
|
|
649
|
-
raise(ActiveRecord::RecordInvalid, model) if options[:raise_error]
|
|
650
|
-
array_of_attributes[i] = nil
|
|
651
|
-
failure = model.dup
|
|
652
|
-
failure.errors.send(:initialize_dup, model.errors)
|
|
653
|
-
failed_instances << failure
|
|
654
|
-
end
|
|
655
|
-
end
|
|
656
|
-
|
|
657
|
-
array_of_attributes.compact!
|
|
658
|
-
|
|
659
|
-
result = if array_of_attributes.empty? || options[:all_or_none] && failed_instances.any?
|
|
760
|
+
result = if options[:all_or_none] && failed_instances.any?
|
|
660
761
|
ActiveRecord::Import::Result.new([], 0, [], [])
|
|
661
762
|
else
|
|
662
763
|
import_without_validations_or_callbacks( column_names, array_of_attributes, options )
|
|
@@ -671,27 +772,30 @@ class ActiveRecord::Base
|
|
|
671
772
|
# information on +column_names+, +array_of_attributes_ and
|
|
672
773
|
# +options+.
|
|
673
774
|
def import_without_validations_or_callbacks( column_names, array_of_attributes, options = {} )
|
|
775
|
+
return ActiveRecord::Import::Result.new([], 0, [], []) if array_of_attributes.empty?
|
|
776
|
+
|
|
674
777
|
column_names = column_names.map(&:to_sym)
|
|
675
778
|
scope_columns, scope_values = scope_attributes.to_a.transpose
|
|
676
779
|
|
|
677
780
|
unless scope_columns.blank?
|
|
678
781
|
scope_columns.zip(scope_values).each do |name, value|
|
|
679
782
|
name_as_sym = name.to_sym
|
|
680
|
-
next if column_names.include?(name_as_sym)
|
|
681
|
-
|
|
682
|
-
is_sti = (name_as_sym == inheritance_column.to_sym && self < base_class)
|
|
683
|
-
value = Array(value).first if is_sti
|
|
684
|
-
|
|
783
|
+
next if column_names.include?(name_as_sym) || name_as_sym == inheritance_column.to_sym
|
|
685
784
|
column_names << name_as_sym
|
|
686
785
|
array_of_attributes.each { |attrs| attrs << value }
|
|
687
786
|
end
|
|
688
787
|
end
|
|
689
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
|
+
|
|
690
796
|
columns = column_names.each_with_index.map do |name, i|
|
|
691
797
|
column = columns_hash[name.to_s]
|
|
692
|
-
|
|
693
798
|
raise ActiveRecord::Import::MissingColumnError.new(name.to_s, i) if column.nil?
|
|
694
|
-
|
|
695
799
|
column
|
|
696
800
|
end
|
|
697
801
|
|
|
@@ -707,17 +811,29 @@ class ActiveRecord::Base
|
|
|
707
811
|
if supports_import?
|
|
708
812
|
# generate the sql
|
|
709
813
|
post_sql_statements = connection.post_sql_statements( quoted_table_name, options )
|
|
814
|
+
import_size = values_sql.size
|
|
815
|
+
|
|
816
|
+
batch_size = options[:batch_size] || import_size
|
|
817
|
+
run_proc = options[:batch_size].to_i.positive? && options[:batch_progress].respond_to?( :call )
|
|
818
|
+
progress_proc = options[:batch_progress]
|
|
819
|
+
current_batch = 0
|
|
820
|
+
batches = (import_size / batch_size.to_f).ceil
|
|
710
821
|
|
|
711
|
-
batch_size = options[:batch_size] || values_sql.size
|
|
712
822
|
values_sql.each_slice(batch_size) do |batch_values|
|
|
823
|
+
batch_started_at = Time.now.to_i
|
|
824
|
+
|
|
713
825
|
# perform the inserts
|
|
714
826
|
result = connection.insert_many( [insert_sql, post_sql_statements].flatten,
|
|
715
827
|
batch_values,
|
|
716
828
|
options,
|
|
717
|
-
"#{model_name} Create Many
|
|
829
|
+
"#{model_name} Create Many" )
|
|
830
|
+
|
|
718
831
|
number_inserted += result.num_inserts
|
|
719
832
|
ids += result.ids
|
|
720
833
|
results += result.results
|
|
834
|
+
current_batch += 1
|
|
835
|
+
|
|
836
|
+
progress_proc.call(import_size, batches, current_batch, Time.now.to_i - batch_started_at) if run_proc
|
|
721
837
|
end
|
|
722
838
|
else
|
|
723
839
|
transaction(requires_new: true) do
|
|
@@ -743,29 +859,56 @@ class ActiveRecord::Base
|
|
|
743
859
|
model.id = id
|
|
744
860
|
|
|
745
861
|
timestamps.each do |attr, value|
|
|
746
|
-
model.send(attr + "=", value)
|
|
862
|
+
model.send(attr + "=", value) if model.send(attr).nil?
|
|
747
863
|
end
|
|
748
864
|
end
|
|
749
865
|
end
|
|
750
866
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
867
|
+
deserialize_value = lambda do |column, value|
|
|
868
|
+
column = columns_hash[column]
|
|
869
|
+
return value unless column
|
|
870
|
+
if respond_to?(:type_caster)
|
|
871
|
+
type = type_for_attribute(column.name)
|
|
872
|
+
type.deserialize(value)
|
|
873
|
+
elsif column.respond_to?(:type_cast_from_database)
|
|
874
|
+
column.type_cast_from_database(value)
|
|
875
|
+
else
|
|
876
|
+
value
|
|
877
|
+
end
|
|
878
|
+
end
|
|
879
|
+
|
|
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|
|
|
755
895
|
model = models[index]
|
|
756
896
|
|
|
757
897
|
if single_column
|
|
758
|
-
|
|
898
|
+
set_value.call(model, single_column, result)
|
|
759
899
|
else
|
|
760
900
|
columns.each_with_index do |column, col_index|
|
|
761
|
-
|
|
901
|
+
set_value.call(model, column, result[col_index])
|
|
762
902
|
end
|
|
763
903
|
end
|
|
764
904
|
end
|
|
765
905
|
end
|
|
766
906
|
|
|
767
907
|
models.each do |model|
|
|
768
|
-
if model.respond_to?(:
|
|
908
|
+
if model.respond_to?(:changes_applied) # Rails 4.1.8 and higher
|
|
909
|
+
model.changes_internally_applied if model.respond_to?(:changes_internally_applied) # legacy behavior for Rails 5.1
|
|
910
|
+
model.changes_applied
|
|
911
|
+
elsif model.respond_to?(:clear_changes_information) # Rails 4.0 and higher
|
|
769
912
|
model.clear_changes_information
|
|
770
913
|
else # Rails 3.2
|
|
771
914
|
model.instance_variable_get(:@changed_attributes).clear
|
|
@@ -776,14 +919,21 @@ class ActiveRecord::Base
|
|
|
776
919
|
|
|
777
920
|
# Sync belongs_to association ids with foreign key field
|
|
778
921
|
def load_association_ids(model)
|
|
922
|
+
changed_columns = model.changed
|
|
779
923
|
association_reflections = model.class.reflect_on_all_associations(:belongs_to)
|
|
780
924
|
association_reflections.each do |association_reflection|
|
|
781
925
|
next if association_reflection.options[:polymorphic]
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
926
|
+
|
|
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))
|
|
787
937
|
end
|
|
788
938
|
end
|
|
789
939
|
end
|
|
@@ -797,12 +947,14 @@ class ActiveRecord::Base
|
|
|
797
947
|
associated_objects_by_class = {}
|
|
798
948
|
models.each { |model| find_associated_objects_for_import(associated_objects_by_class, model) }
|
|
799
949
|
|
|
800
|
-
# :on_duplicate_key_update
|
|
801
|
-
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
|
|
953
|
+
options.delete(:returning)
|
|
802
954
|
|
|
803
955
|
associated_objects_by_class.each_value do |associations|
|
|
804
956
|
associations.each_value do |associated_records|
|
|
805
|
-
associated_records.first.class.
|
|
957
|
+
associated_records.first.class.bulk_import(associated_records, options) unless associated_records.empty?
|
|
806
958
|
end
|
|
807
959
|
end
|
|
808
960
|
end
|
|
@@ -829,8 +981,13 @@ class ActiveRecord::Base
|
|
|
829
981
|
changed_objects.each do |child|
|
|
830
982
|
child.public_send("#{association_reflection.foreign_key}=", model.id)
|
|
831
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
|
|
832
989
|
association_reflection.type.try do |type|
|
|
833
|
-
child.public_send("#{type}=",
|
|
990
|
+
child.public_send("#{type}=", association_name)
|
|
834
991
|
end
|
|
835
992
|
end
|
|
836
993
|
associated_objects_by_class[model.class.name][association_reflection.name].concat changed_objects
|
|
@@ -850,14 +1007,14 @@ class ActiveRecord::Base
|
|
|
850
1007
|
column = columns[j]
|
|
851
1008
|
|
|
852
1009
|
# be sure to query sequence_name *last*, only if cheaper tests fail, because it's costly
|
|
853
|
-
if val.nil? &&
|
|
1010
|
+
if val.nil? && Array(primary_key).first == column.name && !sequence_name.blank?
|
|
854
1011
|
connection_memo.next_value_for_sequence(sequence_name)
|
|
855
1012
|
elsif val.respond_to?(:to_sql)
|
|
856
1013
|
"(#{val.to_sql})"
|
|
857
1014
|
elsif column
|
|
858
1015
|
if respond_to?(:type_caster) # Rails 5.0 and higher
|
|
859
1016
|
type = type_for_attribute(column.name)
|
|
860
|
-
val = type.type == :boolean ? type.cast(val) : type.serialize(val)
|
|
1017
|
+
val = !type.respond_to?(:subtype) && type.type == :boolean ? type.cast(val) : type.serialize(val)
|
|
861
1018
|
connection_memo.quote(val)
|
|
862
1019
|
elsif column.respond_to?(:type_cast_from_user) # Rails 4.2
|
|
863
1020
|
connection_memo.quote(column.type_cast_from_user(val), column)
|
|
@@ -866,9 +1023,11 @@ class ActiveRecord::Base
|
|
|
866
1023
|
val = serialized_attributes[column.name].dump(val)
|
|
867
1024
|
end
|
|
868
1025
|
# Fixes #443 to support binary (i.e. bytea) columns on PG
|
|
869
|
-
val = column.type_cast(val) unless column.type.to_sym == :binary
|
|
1026
|
+
val = column.type_cast(val) unless column.type && column.type.to_sym == :binary
|
|
870
1027
|
connection_memo.quote(val, column)
|
|
871
1028
|
end
|
|
1029
|
+
else
|
|
1030
|
+
raise ArgumentError, "Number of values (#{arr.length}) exceeds number of columns (#{columns.length})"
|
|
872
1031
|
end
|
|
873
1032
|
end
|
|
874
1033
|
"(#{my_values.join(',')})"
|
|
@@ -883,13 +1042,18 @@ class ActiveRecord::Base
|
|
|
883
1042
|
timestamp_columns[:create] = timestamp_attributes_for_create_in_model
|
|
884
1043
|
timestamp_columns[:update] = timestamp_attributes_for_update_in_model
|
|
885
1044
|
else
|
|
886
|
-
instance =
|
|
1045
|
+
instance = allocate
|
|
887
1046
|
timestamp_columns[:create] = instance.send(:timestamp_attributes_for_create_in_model)
|
|
888
1047
|
timestamp_columns[:update] = instance.send(:timestamp_attributes_for_update_in_model)
|
|
889
1048
|
end
|
|
890
1049
|
|
|
891
1050
|
# use tz as set in ActiveRecord::Base
|
|
892
|
-
|
|
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
|
|
893
1057
|
|
|
894
1058
|
[:create, :update].each do |action|
|
|
895
1059
|
timestamp_columns[action].each do |column|
|
|
@@ -899,7 +1063,7 @@ class ActiveRecord::Base
|
|
|
899
1063
|
index = column_names.index(column) || column_names.index(column.to_sym)
|
|
900
1064
|
if index
|
|
901
1065
|
# replace every instance of the array of attributes with our value
|
|
902
|
-
array_of_attributes.each { |arr| arr[index] = timestamp if arr[index].nil?
|
|
1066
|
+
array_of_attributes.each { |arr| arr[index] = timestamp if arr[index].nil? }
|
|
903
1067
|
else
|
|
904
1068
|
column_names << column
|
|
905
1069
|
array_of_attributes.each { |arr| arr << timestamp }
|
|
@@ -934,7 +1098,7 @@ Hash key mismatch.
|
|
|
934
1098
|
|
|
935
1099
|
When importing an array of hashes with provided columns_names, each hash must contain keys for all column_names.
|
|
936
1100
|
|
|
937
|
-
Required keys: #{
|
|
1101
|
+
Required keys: #{required_keys}
|
|
938
1102
|
Missing keys: #{missing_keys}
|
|
939
1103
|
|
|
940
1104
|
Hash: #{hash}
|
|
@@ -947,7 +1111,7 @@ When importing an array of hashes, all hashes must have the same keys.
|
|
|
947
1111
|
If you have records that are missing some values, we recommend you either set default values
|
|
948
1112
|
for the missing keys or group these records into batches by key set before importing.
|
|
949
1113
|
|
|
950
|
-
Required keys: #{
|
|
1114
|
+
Required keys: #{required_keys}
|
|
951
1115
|
Extra keys: #{extra_keys}
|
|
952
1116
|
Missing keys: #{missing_keys}
|
|
953
1117
|
|