activerecord-import 2.3.0 → 3.0.0

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.
@@ -23,126 +23,84 @@ module ActiveRecord::Import # :nodoc:
23
23
  end
24
24
  end
25
25
 
26
- class Validator
27
- def initialize(klass, options = {})
28
- @options = options
29
- @validator_class = klass
30
- init_validations(klass)
26
+ # Skips uniqueness validations while a model is being validated for an import
27
+ # that doesn't validate uniqueness, including the validations Rails runs on
28
+ # the model's associated records.
29
+ module SkipUniquenessValidation # :nodoc:
30
+ def validate_each(record, attribute, value)
31
+ super unless ActiveRecord::Import::Validator.skip_uniqueness?
31
32
  end
33
+ end
32
34
 
33
- def init_validations(klass)
34
- @validate_callbacks = klass._validate_callbacks.dup
35
+ # Validates the presence of a belongs_to association on a model being
36
+ # validated for an import by checking its foreign key, so the associated
37
+ # record isn't loaded for every model.
38
+ module ForeignKeyPresenceValidation # :nodoc:
39
+ def validate(record)
40
+ validator = ActiveRecord::Import::Validator.foreign_key_presence_validator(self, record)
41
+ return super unless validator
35
42
 
36
- @validate_callbacks.each_with_index do |callback, i|
37
- filter = callback.respond_to?(:raw_filter) ? callback.raw_filter : callback.filter
38
- next unless filter.class.name =~ /Validations::PresenceValidator/ ||
39
- (!@options[:validate_uniqueness] &&
40
- filter.is_a?(ActiveRecord::Validations::UniquenessValidator))
43
+ ActiveModel::EachValidator.instance_method(:validate).bind(validator).call(record)
44
+ end
45
+ end
41
46
 
42
- callback = callback.dup
43
- filter = filter.dup
44
- attrs = filter.instance_variable_get(:@attributes).dup
47
+ class Validator
48
+ # Holds [validator, model] for the current thread while valid_model? runs.
49
+ CURRENT = :activerecord_import_validation
45
50
 
46
- if filter.is_a?(ActiveRecord::Validations::UniquenessValidator)
47
- attrs = []
48
- else
49
- associations = klass.reflect_on_all_associations(:belongs_to)
50
- associations.each do |assoc|
51
- if (index = attrs.index(assoc.name))
52
- key = assoc.foreign_key.is_a?(Array) ? assoc.foreign_key.map(&:to_sym) : assoc.foreign_key.to_sym
53
- attrs[index] = key unless attrs.include?(key)
54
- end
55
- end
56
- end
51
+ attr_reader :options
57
52
 
58
- filter.instance_variable_set(:@attributes, attrs.flatten)
53
+ def self.skip_uniqueness?
54
+ validator, = Thread.current[CURRENT]
55
+ validator ? !validator.options[:validate_uniqueness] : false
56
+ end
59
57
 
60
- if @validate_callbacks.respond_to?(:chain, true)
61
- @validate_callbacks.send(:chain).tap do |chain|
62
- callback.instance_variable_set(:@filter, filter)
63
- callback.instance_variable_set(:@compiled, nil)
64
- chain[i] = callback
65
- end
66
- else
67
- callback.raw_filter = filter
68
- callback.filter = callback.send(:_compile_filter, filter)
69
- @validate_callbacks[i] = callback
70
- end
71
- end
58
+ def self.foreign_key_presence_validator(presence_validator, record)
59
+ validator, model = Thread.current[CURRENT]
60
+ validator.foreign_key_presence_validator(presence_validator, record.class) if model.equal?(record)
72
61
  end
73
62
 
74
- def valid_model?(model)
75
- init_validations(model.class) unless model.instance_of?(@validator_class)
76
-
77
- validation_context = @options[:validate_with_context]
78
- validation_context ||= (model.new_record? ? :create : :update)
79
- current_context = model.send(:validation_context)
80
-
81
- begin
82
- set_validation_context(model, validation_context)
83
- model.errors.clear
84
-
85
- model.run_callbacks(:validation) do
86
- if defined?(ActiveSupport::Callbacks::Filters::Environment) # ActiveRecord >= 4.1
87
- runner = if @validate_callbacks.method(:compile).arity == 0
88
- @validate_callbacks.compile
89
- else # ActiveRecord >= 7.1
90
- @validate_callbacks.compile(nil)
91
- end
92
- env = ActiveSupport::Callbacks::Filters::Environment.new(model, false, nil)
93
- if runner.respond_to?(:call) # ActiveRecord < 5.1
94
- runner.call(env)
95
- else # ActiveRecord >= 5.1
96
- # Note that this is a gross simplification of ActiveSupport::Callbacks#run_callbacks.
97
- # It's technically possible for there to exist an "around" callback in the
98
- # :validate chain, but this would be an aberration, since Rails doesn't define
99
- # "around_validate". Still, rather than silently ignoring such callbacks, we
100
- # explicitly raise a RuntimeError, since activerecord-import was asked to perform
101
- # validations and it's unable to do so.
102
- #
103
- # The alternative here would be to copy-and-paste the bulk of the
104
- # ActiveSupport::Callbacks#run_callbacks method, which is undesirable if there's
105
- # no real-world use case for it.
106
- raise "The :validate callback chain contains an 'around' callback, which is unsupported" unless runner.final?
107
- runner.invoke_before(env)
108
- # Ensure a truthy value is returned. ActiveRecord < 7.2 always returned an array.
109
- runner.invoke_after(env) || []
110
- end
111
- elsif @validate_callbacks.method(:compile).arity == 0 # ActiveRecord = 4.0
112
- model.instance_eval @validate_callbacks.compile
113
- else # ActiveRecord 3.x
114
- model.instance_eval @validate_callbacks.compile(nil, model)
115
- end
116
- end
63
+ def initialize(options = {})
64
+ @options = options
65
+ @foreign_key_presence_validators = {}
66
+ end
117
67
 
118
- model.errors.empty?
119
- ensure
120
- set_validation_context(model, current_context)
121
- end
68
+ def valid_model?(model)
69
+ current = Thread.current[CURRENT]
70
+ Thread.current[CURRENT] = [self, model]
71
+ model.valid?(@options[:validate_with_context])
72
+ ensure
73
+ Thread.current[CURRENT] = current
122
74
  end
123
75
 
124
- private
76
+ # Returns a copy of +presence_validator+ that validates the foreign keys of
77
+ # any belongs_to associations it validates, or nil if there are none.
78
+ def foreign_key_presence_validator(presence_validator, klass)
79
+ key = [presence_validator, klass]
80
+ return @foreign_key_presence_validators[key] if @foreign_key_presence_validators.key?(key)
81
+
82
+ attrs = presence_validator.attributes.dup
83
+ klass.reflect_on_all_associations(:belongs_to).each do |assoc|
84
+ if (index = attrs.index(assoc.name))
85
+ foreign_key = assoc.foreign_key.is_a?(Array) ? assoc.foreign_key.map(&:to_sym) : assoc.foreign_key.to_sym
86
+ attrs[index] = foreign_key unless attrs.include?(foreign_key)
87
+ end
88
+ end
125
89
 
126
- # ActiveModel's private +validation_context=+ writer was removed in Rails
127
- # 8.2 (rails/rails@0f9d1270834a6407a59637650bf910d8ae826169) in favor of a
128
- # validation context object reachable through the private
129
- # +context_for_validation+ reader. Support both so that validated imports
130
- # keep working across all supported ActiveRecord versions.
131
- def set_validation_context(model, context)
132
- if model.respond_to?(:validation_context=, true)
133
- model.send(:validation_context=, context)
134
- else
135
- model.send(:context_for_validation).context = context
90
+ @foreign_key_presence_validators[key] = if attrs != presence_validator.attributes
91
+ presence_validator.dup.tap { |validator| validator.instance_variable_set(:@attributes, attrs.flatten) }
136
92
  end
137
93
  end
138
94
  end
139
95
  end
140
96
 
97
+ ActiveRecord::Validations::UniquenessValidator.prepend(ActiveRecord::Import::SkipUniquenessValidation)
98
+ ActiveModel::Validations::PresenceValidator.prepend(ActiveRecord::Import::ForeignKeyPresenceValidation)
99
+
141
100
  class ActiveRecord::Associations::CollectionProxy
142
101
  def bulk_import(*args, &block)
143
102
  @association.bulk_import(*args, &block)
144
103
  end
145
- alias import bulk_import unless respond_to? :import
146
104
  end
147
105
 
148
106
  class ActiveRecord::Associations::CollectionAssociation
@@ -154,9 +112,10 @@ class ActiveRecord::Associations::CollectionAssociation
154
112
  options = args.last.is_a?(Hash) ? args.pop : {}
155
113
 
156
114
  model_klass = reflection.klass
115
+ args = model_klass.send(:arrays_from_array_like_import_arguments, args)
157
116
  symbolized_foreign_key = reflection.foreign_key.to_sym
158
117
 
159
- symbolized_column_names = if model_klass.connection_pool.with_connection { |conn| conn.respond_to?(:supports_virtual_columns?) && conn.supports_virtual_columns? }
118
+ symbolized_column_names = if model_klass.connection_pool.with_connection(&:supports_virtual_columns?)
160
119
  model_klass.columns.reject(&:virtual?).map { |c| c.name.to_sym }
161
120
  else
162
121
  model_klass.column_names.map(&:to_sym)
@@ -164,12 +123,14 @@ class ActiveRecord::Associations::CollectionAssociation
164
123
 
165
124
  owner_primary_key = reflection.active_record_primary_key.to_sym
166
125
  owner_primary_key_value = owner.send(owner_primary_key)
126
+ owner_polymorphic_name = owner.class.polymorphic_name if reflection.type
167
127
 
168
128
  # assume array of model objects
169
129
  if args.last.is_a?( Array ) && args.last.first.is_a?(ActiveRecord::Base)
170
130
  if args.length == 2
171
131
  models = args.last
172
132
  column_names = args.first.dup
133
+ symbolized_column_names = column_names.map(&:to_sym)
173
134
  else
174
135
  models = args.first
175
136
  column_names = symbolized_column_names
@@ -179,9 +140,13 @@ class ActiveRecord::Associations::CollectionAssociation
179
140
  column_names << symbolized_foreign_key
180
141
  end
181
142
 
143
+ if reflection.type && !symbolized_column_names.include?(reflection.type.to_sym)
144
+ column_names << reflection.type.to_sym
145
+ end
146
+
182
147
  models.each do |m|
183
148
  m.public_send "#{symbolized_foreign_key}=", owner_primary_key_value
184
- m.public_send "#{reflection.type}=", owner.class.name if reflection.type
149
+ m.public_send "#{reflection.type}=", owner_polymorphic_name if reflection.type
185
150
  end
186
151
 
187
152
  model_klass.bulk_import column_names, models, options
@@ -198,6 +163,7 @@ class ActiveRecord::Associations::CollectionAssociation
198
163
  allow_extra_hash_keys = false
199
164
  end
200
165
 
166
+ required_column_names = column_names.dup
201
167
  symbolized_column_names = column_names.map(&:to_sym)
202
168
  unless symbolized_column_names.include?(symbolized_foreign_key)
203
169
  column_names << symbolized_foreign_key
@@ -208,15 +174,16 @@ class ActiveRecord::Associations::CollectionAssociation
208
174
  end
209
175
 
210
176
  array_of_attributes = array_of_hashes.map do |h|
211
- error_message = model_klass.send(:validate_hash_import, h, symbolized_column_names, allow_extra_hash_keys)
177
+ error_message = model_klass.send(:validate_hash_import, h, required_column_names, allow_extra_hash_keys)
212
178
 
213
179
  raise ArgumentError, error_message if error_message
214
180
 
215
181
  column_names.map do |key|
216
- if key == symbolized_foreign_key
182
+ symbolized_key = key.to_sym
183
+ if symbolized_key == symbolized_foreign_key
217
184
  owner_primary_key_value
218
- elsif reflection.type && key == reflection.type.to_sym
219
- owner.class.name
185
+ elsif reflection.type && symbolized_key == reflection.type.to_sym
186
+ owner_polymorphic_name
220
187
  else
221
188
  h[key]
222
189
  end
@@ -251,19 +218,18 @@ class ActiveRecord::Associations::CollectionAssociation
251
218
  symbolized_type = reflection.type.to_sym
252
219
  if symbolized_column_names.include?(symbolized_type)
253
220
  index = symbolized_column_names.index(symbolized_type)
254
- array_of_attributes.each { |attrs| attrs[index] = owner.class.name }
221
+ array_of_attributes.each { |attrs| attrs[index] = owner_polymorphic_name }
255
222
  else
256
223
  column_names << symbolized_type
257
- array_of_attributes.each { |attrs| attrs << owner.class.name }
224
+ array_of_attributes.each { |attrs| attrs << owner_polymorphic_name }
258
225
  end
259
226
  end
260
227
 
261
228
  model_klass.bulk_import column_names, array_of_attributes, options
262
229
  else
263
- raise ArgumentError, "Invalid arguments!"
230
+ raise ArgumentError, model_klass.send(:invalid_import_arguments_message, args)
264
231
  end
265
232
  end
266
- alias import bulk_import unless respond_to? :import
267
233
  end
268
234
 
269
235
  module ActiveRecord::Import::ConnectionHandler
@@ -383,7 +349,7 @@ class ActiveRecord::Base
383
349
  # (if false) even if record timestamps is disabled in ActiveRecord::Base
384
350
  # * +recursive+ - true|false, tells import to import all has_many/has_one
385
351
  # associations if the adapter supports setting the primary keys of the
386
- # newly imported objects. PostgreSQL only.
352
+ # newly imported objects. PostgreSQL and MariaDB 10.5+ only.
387
353
  # * +batch_size+ - an integer value to specify the max number of records to
388
354
  # include per insert. Defaults to the total number of records to import.
389
355
  #
@@ -438,7 +404,8 @@ class ActiveRecord::Base
438
404
  # ==== Using :all
439
405
  #
440
406
  # The :on_duplicate_key_update option can be set to :all. All columns
441
- # other than the primary key are updated. If a list of column names is
407
+ # other than the primary key are updated, except for timestamps recorded
408
+ # on create, such as created_at. If a list of column names is
442
409
  # supplied, only those columns will be updated. Below is an example:
443
410
  #
444
411
  # BlogPost.import columns, values, on_duplicate_key_update: :all
@@ -469,7 +436,8 @@ class ActiveRecord::Base
469
436
  # ==== Using :all
470
437
  #
471
438
  # The :on_duplicate_key_update option can be set to :all. All columns
472
- # other than the primary key are updated. If a list of column names is
439
+ # other than the primary key are updated, except for timestamps recorded
440
+ # on create, such as created_at. If a list of column names is
473
441
  # supplied, only those columns will be updated. Below is an example:
474
442
  #
475
443
  # BlogPost.import columns, values, on_duplicate_key_update: :all
@@ -534,7 +502,8 @@ class ActiveRecord::Base
534
502
  #
535
503
  # ===== Using :all
536
504
  #
537
- # The :columns attribute can be :all. All columns other than the primary key will be updated.
505
+ # The :columns attribute can be :all. All columns other than the primary key will be updated,
506
+ # except for timestamps recorded on create, such as created_at.
538
507
  # If a list of column names is supplied, only those columns will be updated.
539
508
  # Below is an example:
540
509
  #
@@ -573,160 +542,58 @@ class ActiveRecord::Base
573
542
  import_helper(*args)
574
543
  end
575
544
  end
576
- alias import bulk_import unless ActiveRecord::Base.respond_to? :import
577
545
 
578
546
  # Imports a collection of values if all values are valid. Import fails at the
579
547
  # first encountered validation error and raises ActiveRecord::RecordInvalid
580
548
  # with the failed instance.
581
549
  def bulk_import!(*args)
582
550
  options = args.last.is_a?( Hash ) ? args.pop : {}
583
- options[:validate] = true
584
- options[:raise_error] = true
551
+ options = options.merge( validate: true, raise_error: true )
585
552
 
586
553
  bulk_import(*args, options)
587
554
  end
588
- alias import! bulk_import! unless ActiveRecord::Base.respond_to? :import!
555
+
556
+ # Returns the column for +name+, following an attribute alias unless +name+ is
557
+ # itself a column, so a column that's also an alias isn't replaced by the
558
+ # column it aliases.
559
+ def column_name_for_import(name) # :nodoc:
560
+ name = name.to_s
561
+ attribute_alias?(name) && !columns_hash.key?(name) ? attribute_alias(name) : name
562
+ end
589
563
 
590
564
  def import_helper( *args )
591
565
  connection_pool.with_connection do |conn|
592
- options = { model: self, validate: true, timestamps: true, track_validation_failures: false }
593
- options.merge!( args.pop ) if args.last.is_a? Hash
594
- # making sure that current model's primary key is used
595
- options[:primary_key] = primary_key
596
- options[:locking_column] = locking_column if locking_enabled?
566
+ # The adapter's import support is loaded when the connection is established, which is
567
+ # missed if activerecord-import is required afterwards, so load it now if it's missing.
568
+ ActiveRecord::Import.load_from_connection_pool_once(connection_pool) unless supports_import?(conn)
569
+
570
+ options, args = options_and_arguments_for_import(args)
597
571
 
598
572
  is_validating = options[:validate_with_context].present? || options[:validate]
599
- validator = ActiveRecord::Import::Validator.new(self, options)
573
+ validator = ActiveRecord::Import::Validator.new(options) if is_validating
574
+
575
+ # supports empty array
576
+ return ActiveRecord::Import::Result.new([], 0, [], []) if args.last.is_a?( Array ) && args.last.empty?
600
577
 
601
578
  # assume array of model objects
602
579
  if args.last.is_a?( Array ) && args.last.first.is_a?(ActiveRecord::Base)
603
- if args.length == 2
604
- models = args.last
605
- column_names = args.first.dup
606
- else
607
- models = args.first
608
- column_names = if conn.try(:supports_virtual_columns?)
609
- columns.reject(&:virtual?).map(&:name)
610
- else
611
- self.column_names.dup
612
- end
613
- end
614
-
615
- if models.first.id.nil?
616
- Array(primary_key).each do |c|
617
- if column_names.include?(c) && schema_columns_hash(conn)[c].type == :uuid
618
- column_names.delete(c)
619
- end
620
- end
621
- end
622
-
623
- update_attrs = if record_timestamps && options[:timestamps]
624
- if respond_to?(:timestamp_attributes_for_update, true)
625
- send(:timestamp_attributes_for_update).map(&:to_sym)
626
- else
627
- allocate.send(:timestamp_attributes_for_update_in_model)
628
- end
629
- end
630
-
631
- array_of_attributes = []
632
-
633
- models.each do |model|
634
- if supports_setting_primary_key_of_imported_objects?(conn)
635
- load_association_ids(model)
636
- end
637
-
638
- if is_validating && !validator.valid_model?(model)
639
- raise(ActiveRecord::RecordInvalid, model) if options[:raise_error]
640
- next
641
- end
642
-
643
- array_of_attributes << column_names.map do |name|
644
- if model.persisted? &&
645
- update_attrs && update_attrs.include?(name.to_sym) &&
646
- !model.send("#{name}_changed?")
647
- nil
648
- else
649
- model.read_attribute(name.to_s)
650
- end
651
- end
652
- end
653
- # supports array of hash objects
654
- elsif args.last.is_a?( Array ) && args.last.first.is_a?(Hash)
655
- if args.length == 2
656
- array_of_hashes = args.last
657
- column_names = args.first.dup
658
- allow_extra_hash_keys = true
659
- else
660
- array_of_hashes = args.first
661
- column_names = array_of_hashes.first.keys
662
- allow_extra_hash_keys = false
663
- end
664
-
665
- array_of_attributes = array_of_hashes.map do |h|
666
- error_message = validate_hash_import(h, column_names, allow_extra_hash_keys)
667
-
668
- raise ArgumentError, error_message if error_message
669
-
670
- column_names.map do |key|
671
- h[key]
672
- end
673
- end
674
- # supports empty array
675
- elsif args.last.is_a?( Array ) && args.last.empty?
676
- return ActiveRecord::Import::Result.new([], 0, [], [])
677
- # supports 2-element array and array
678
- elsif args.size == 2 && args.first.is_a?( Array ) && args.last.is_a?( Array )
679
-
680
- unless args.last.first.is_a?(Array)
681
- raise ArgumentError, "Last argument should be a two dimensional array '[[]]'. First element in array was a #{args.last.first.class}"
682
- end
683
-
684
- column_names, array_of_attributes = args
685
-
686
- # dup the passed args so we don't modify unintentionally
687
- column_names = column_names.dup
688
- array_of_attributes = array_of_attributes.map(&:dup)
580
+ models, column_names, default_column_names = models_and_column_names_for_import(conn, args)
581
+ array_of_attributes = attributes_from_models(conn, models, column_names, validator, options)
689
582
  else
690
- raise ArgumentError, "Invalid arguments!"
583
+ column_names, array_of_attributes = column_names_and_attributes_for_import(args)
691
584
  end
692
585
 
693
- # Force the primary key col into the insert if it's not
694
- # on the list and we are using a sequence and stuff a nil
695
- # value for it into each row so the sequencer will fire later
696
- symbolized_column_names = Array(column_names).map(&:to_sym)
697
- symbolized_primary_key = Array(primary_key).map(&:to_sym)
698
-
699
- if !symbolized_primary_key.to_set.subset?(symbolized_column_names.to_set) && conn.prefetch_primary_key? && sequence_name
700
- column_count = column_names.size
701
- column_names.concat(Array(primary_key)).uniq!
702
- columns_added = column_names.size - column_count
703
- new_fields = Array.new(columns_added)
704
- array_of_attributes.each { |a| a.concat(new_fields) }
705
- end
586
+ # Rows without a value for these columns get the database's default rather than NULL
587
+ options[:database_default_columns] = Array(default_column_names) | database_populated_primary_keys(conn)
706
588
 
707
589
  # Don't modify incoming arguments
708
590
  on_duplicate_key_update = options[:on_duplicate_key_update]
709
591
  if on_duplicate_key_update
710
- updatable_columns = symbolized_column_names.reject { |c| symbolized_primary_key.include? c }
711
- options[:on_duplicate_key_update] = if on_duplicate_key_update.is_a?(Hash)
712
- on_duplicate_key_update.each_with_object({}) do |(k, v), duped_options|
713
- duped_options[k] = if k == :columns && v == :all
714
- updatable_columns
715
- elsif v.duplicable?
716
- v.dup
717
- else
718
- v
719
- end
720
- end
721
- elsif on_duplicate_key_update == :all
722
- updatable_columns
723
- elsif on_duplicate_key_update.duplicable?
724
- on_duplicate_key_update.dup
725
- else
726
- on_duplicate_key_update
727
- end
592
+ options[:on_duplicate_key_update] = on_duplicate_key_update_for_import(on_duplicate_key_update, column_names, options)
728
593
  end
729
594
 
595
+ add_primary_key_for_sequence(conn, column_names, array_of_attributes)
596
+
730
597
  timestamps = {}
731
598
 
732
599
  # record timestamps unless disabled in ActiveRecord::Base
@@ -737,41 +604,20 @@ class ActiveRecord::Base
737
604
  return_obj = if is_validating
738
605
  import_with_validations( conn, column_names, array_of_attributes, options ) do |failed_instances|
739
606
  if models
740
- models.each_with_index do |m, i|
741
- next unless m.errors.any?
742
-
743
- failed_instances << (options[:track_validation_failures] ? [i, m] : m)
744
- end
607
+ add_failed_models(failed_instances, models, options)
745
608
  else
746
- # create instances for each of our column/value sets
747
- arr = validations_array_for_column_names_and_attributes( column_names, array_of_attributes )
748
-
749
- # keep track of the instance and the position it is currently at. if this fails
750
- # validation we'll use the index to remove it from the array_of_attributes
751
- arr.each_with_index do |hsh, i|
752
- # utilize block initializer syntax to prevent failure when 'mass_assignment_sanitizer = :strict'
753
- model = new do |m|
754
- hsh.each_pair { |k, v| m[k] = v }
755
- end
756
-
757
- next if validator.valid_model?(model)
758
- raise(ActiveRecord::RecordInvalid, model) if options[:raise_error]
759
-
760
- array_of_attributes[i] = nil
761
- failure = model.dup
762
- failure.errors.send(:initialize_dup, model.errors)
763
- failed_instances << (options[:track_validation_failures] ? [i, failure] : failure )
764
- end
765
- array_of_attributes.compact!
609
+ validate_attributes_for_import(failed_instances, column_names, array_of_attributes, validator, options)
766
610
  end
767
611
  end
768
612
  else
769
613
  import_without_validations_or_callbacks( conn, column_names, array_of_attributes, options )
770
614
  end
771
615
 
616
+ return return_obj if options[:all_or_none] && return_obj.failed_instances.any?
617
+
772
618
  if options[:synchronize]
773
619
  sync_keys = options[:synchronize_keys] || Array(primary_key)
774
- synchronize( options[:synchronize], sync_keys)
620
+ synchronized_instances = synchronize_imported_instances(options[:synchronize], sync_keys)
775
621
  end
776
622
  return_obj.num_inserts = 0 if return_obj.num_inserts.nil?
777
623
 
@@ -781,11 +627,14 @@ class ActiveRecord::Base
781
627
 
782
628
  # if there are auto-save associations on the models we imported that are new, import them as well
783
629
  if options[:recursive]
784
- options[:on_duplicate_key_update] = on_duplicate_key_update unless on_duplicate_key_update.nil?
785
- import_associations(models, options.dup.merge(validate: false))
630
+ import_associations_of_imported_models(models, return_obj, on_duplicate_key_update, options)
786
631
  end
787
632
  end
788
633
 
634
+ # apply the changes of synchronized instances that weren't marked clean above,
635
+ # such as when importing columns and values rather than models
636
+ synchronized_instances&.each { |instance| instance.changes_applied if instance.changed? }
637
+
789
638
  return_obj
790
639
  end
791
640
  end
@@ -820,8 +669,7 @@ class ActiveRecord::Base
820
669
  return ActiveRecord::Import::Result.new([], 0, [], []) if array_of_attributes.empty?
821
670
 
822
671
  column_names = column_names.map do |name|
823
- original_name = attribute_alias?(name) ? attribute_alias(name) : name
824
- original_name.to_sym
672
+ column_name_for_import(name).to_sym
825
673
  end
826
674
  scope_columns, scope_values = scope_attributes.to_a.transpose
827
675
 
@@ -849,7 +697,8 @@ class ActiveRecord::Base
849
697
  pre_sql_statements = conn.pre_sql_statements( options )
850
698
  insert_sql = ['INSERT', pre_sql_statements, "INTO #{quoted_table_name} #{columns_sql} VALUES "]
851
699
  insert_sql = insert_sql.flatten.join(' ')
852
- values_sql = values_sql_for_columns_and_attributes(conn, columns, array_of_attributes)
700
+ values_sql = values_sql_for_columns_and_attributes(conn, columns, array_of_attributes, Array(options[:database_default_columns]))
701
+ comment_sql = sql_comment_for_annotations(conn)
853
702
 
854
703
  number_inserted = 0
855
704
  ids = []
@@ -859,6 +708,7 @@ class ActiveRecord::Base
859
708
  if supports_import?(conn)
860
709
  # generate the sql
861
710
  post_sql_statements = conn.post_sql_statements( quoted_table_name, options )
711
+ post_sql_statements += [comment_sql] if comment_sql
862
712
  import_size = values_sql.size
863
713
 
864
714
  batch_size = options[:batch_size] || import_size
@@ -877,17 +727,21 @@ class ActiveRecord::Base
877
727
  "#{model_name} Create Many" )
878
728
 
879
729
  number_inserted += result.num_inserts
880
- ids += result.ids
881
- results += result.results
730
+ ids.concat(result.ids)
731
+ results.concat(result.results)
882
732
  affected_rows = result.affected_rows.nil? ? nil : (affected_rows || 0) + result.affected_rows
883
733
  current_batch += 1
884
734
 
885
735
  progress_proc.call(import_size, batches, current_batch, Time.now.to_i - batch_started_at) if run_proc
886
736
  end
887
737
  else
738
+ logger&.warn "activerecord-import: the #{conn.adapter_name} adapter doesn't support bulk import, so #{model_name} " \
739
+ "records are being inserted one at a time. If the adapter is supported under another name, require its " \
740
+ "activerecord-import adapter, e.g. activerecord-import/active_record/adapters/postgresql_adapter."
741
+
888
742
  transaction(requires_new: true) do
889
743
  values_sql.each do |values|
890
- ids << conn.insert(insert_sql + values)
744
+ ids << conn.insert("#{insert_sql}#{values}#{comment_sql}", "#{model_name} Create Many")
891
745
  number_inserted += 1
892
746
  end
893
747
  end
@@ -897,6 +751,239 @@ class ActiveRecord::Base
897
751
 
898
752
  private
899
753
 
754
+ # Takes the options Hash, if any, off the end of +args+ and returns the options
755
+ # for the import, with their defaults, along with the arguments to import.
756
+ def options_and_arguments_for_import(args)
757
+ options = { validate: true, timestamps: true, track_validation_failures: false }
758
+ options.merge!( args.pop ) if args.last.is_a? Hash
759
+ args = arrays_from_array_like_import_arguments(args)
760
+ # making sure that the current model and its primary key are used, rather than
761
+ # the parent model's when importing associations recursively
762
+ options[:model] = self
763
+ options[:primary_key] = primary_key
764
+ options[:locking_column] = locking_column if locking_enabled?
765
+ [options, args]
766
+ end
767
+
768
+ # Returns the models to import from +args+, the column names to import them with,
769
+ # and the columns the database populates for the models that don't assign them,
770
+ # which is nil when +args+ give the column names.
771
+ def models_and_column_names_for_import(conn, args)
772
+ if args.length == 2
773
+ models = args.last
774
+ column_names = args.first.dup
775
+ else
776
+ models = args.first
777
+ column_names = if conn.try(:supports_virtual_columns?)
778
+ columns.reject(&:virtual?).map(&:name)
779
+ else
780
+ self.column_names.dup
781
+ end
782
+ populated_column_names = database_populated_column_names(conn, column_names, models)
783
+ # Leave out the ones no model assigns, and let the database fill in the rest for the models that don't
784
+ default_column_names = populated_column_names.select { |name| models.any? { |model| model.attribute_changed?(name) } }
785
+ column_names -= populated_column_names - default_column_names
786
+ end
787
+
788
+ # Leave out a primary key the database populates, like a uuid, only when no model has one, so
789
+ # assigned ids are kept. Otherwise the database fills it in for the models without one.
790
+ column_names -= database_populated_primary_keys(conn) if models.all? { |model| model.id.nil? }
791
+
792
+ [models, column_names, default_column_names]
793
+ end
794
+
795
+ # Returns the values of +column_names+ for each of +models+, the rows to import. Models
796
+ # that +validator+ finds invalid are left out, or raise ActiveRecord::RecordInvalid with
797
+ # the :raise_error option. Without a +validator+, the models aren't validated.
798
+ def attributes_from_models(conn, models, column_names, validator, options)
799
+ update_attrs = if record_timestamps && options[:timestamps]
800
+ send(:timestamp_attributes_for_update).map(&:to_sym)
801
+ end
802
+
803
+ array_of_attributes = []
804
+
805
+ models.each do |model|
806
+ if supports_setting_primary_key_of_imported_objects?(conn)
807
+ load_association_ids(model)
808
+ end
809
+
810
+ if validator && !validator.valid_model?(model)
811
+ raise(ActiveRecord::RecordInvalid, model) if options[:raise_error]
812
+ next
813
+ end
814
+
815
+ array_of_attributes << column_names.map do |name|
816
+ if model.persisted? &&
817
+ update_attrs && update_attrs.include?(name.to_sym) &&
818
+ !model.send("#{name}_changed?")
819
+ nil
820
+ else
821
+ name = name.to_s
822
+ # Read a column's own value, as saving the model would, even if it's also an alias
823
+ value = columns_hash.key?(name) ? model._read_attribute(name) : model.read_attribute(name)
824
+ model.instance_of?(self) ? value : value_from_model_type(model, name, value)
825
+ end
826
+ end
827
+ end
828
+
829
+ array_of_attributes
830
+ end
831
+
832
+ # Returns the column names and the rows to import from +args+, which are either an
833
+ # array of hashes, optionally after column names, or column names and an array of values.
834
+ def column_names_and_attributes_for_import(args)
835
+ # supports array of hash objects
836
+ if args.last.is_a?( Array ) && args.last.first.is_a?(Hash)
837
+ if args.length == 2
838
+ array_of_hashes = args.last
839
+ column_names = args.first.dup
840
+ allow_extra_hash_keys = true
841
+ else
842
+ array_of_hashes = args.first
843
+ column_names = array_of_hashes.first.keys
844
+ allow_extra_hash_keys = false
845
+ end
846
+
847
+ array_of_attributes = array_of_hashes.map do |h|
848
+ error_message = validate_hash_import(h, column_names, allow_extra_hash_keys)
849
+
850
+ raise ArgumentError, error_message if error_message
851
+
852
+ column_names.map do |key|
853
+ h[key]
854
+ end
855
+ end
856
+ # supports 2-element array and array
857
+ elsif args.size == 2 && args.first.is_a?( Array ) && args.last.is_a?( Array )
858
+
859
+ unless args.last.first.is_a?(Array)
860
+ raise ArgumentError, "Last argument should be a two dimensional array '[[]]'. First element in array was a #{args.last.first.class}"
861
+ end
862
+
863
+ column_names, array_of_attributes = args
864
+
865
+ # dup the passed args so we don't modify unintentionally
866
+ column_names = column_names.dup
867
+ array_of_attributes = array_of_attributes.map(&:dup)
868
+ else
869
+ raise ArgumentError, invalid_import_arguments_message(args)
870
+ end
871
+
872
+ [column_names, array_of_attributes]
873
+ end
874
+
875
+ # Returns a copy of the +on_duplicate_key_update+ option to import with, so it can be
876
+ # changed, such as when adding timestamp columns, without modifying the option that was
877
+ # passed in. :all is replaced by the columns other than the primary key and the
878
+ # timestamps recorded on create.
879
+ def on_duplicate_key_update_for_import(on_duplicate_key_update, column_names, options)
880
+ symbolized_column_names = Array(column_names).map(&:to_sym)
881
+ symbolized_primary_key = Array(primary_key).map(&:to_sym)
882
+
883
+ updatable_columns = symbolized_column_names.reject { |c| symbolized_primary_key.include? c }
884
+ # Like the primary key, timestamps recorded on create shouldn't change when a record is updated
885
+ if record_timestamps && options[:timestamps]
886
+ updatable_columns -= timestamp_attributes_for_create_in_model.map(&:to_sym)
887
+ end
888
+
889
+ if on_duplicate_key_update.is_a?(Hash)
890
+ on_duplicate_key_update.each_with_object({}) do |(k, v), duped_options|
891
+ duped_options[k] = if k == :columns && v == :all
892
+ updatable_columns
893
+ elsif v.duplicable?
894
+ v.dup
895
+ else
896
+ v
897
+ end
898
+ end
899
+ elsif on_duplicate_key_update == :all
900
+ updatable_columns
901
+ elsif on_duplicate_key_update.duplicable?
902
+ on_duplicate_key_update.dup
903
+ else
904
+ on_duplicate_key_update
905
+ end
906
+ end
907
+
908
+ # Force the primary key col into the insert if it's not
909
+ # on the list and we are using a sequence and stuff a nil
910
+ # value for it into each row so the sequencer will fire later
911
+ def add_primary_key_for_sequence(conn, column_names, array_of_attributes)
912
+ symbolized_column_names = Array(column_names).map(&:to_sym)
913
+ symbolized_primary_key = Array(primary_key).map(&:to_sym)
914
+
915
+ if !symbolized_primary_key.to_set.subset?(symbolized_column_names.to_set) && conn.prefetch_primary_key? && sequence_name
916
+ column_count = column_names.size
917
+ column_names.concat(Array(primary_key)).uniq!
918
+ columns_added = column_names.size - column_count
919
+ new_fields = Array.new(columns_added)
920
+ array_of_attributes.each { |a| a.concat(new_fields) }
921
+ end
922
+ end
923
+
924
+ # Adds the models that failed validation, when their rows were read, to +failed_instances+.
925
+ def add_failed_models(failed_instances, models, options)
926
+ models.each_with_index do |m, i|
927
+ next unless m.errors.any?
928
+
929
+ failed_instances << (options[:track_validation_failures] ? [i, m] : m)
930
+ end
931
+ end
932
+
933
+ # Validates each row of +array_of_attributes+ as a new model, adding the models that
934
+ # fail to +failed_instances+ and removing their rows from +array_of_attributes+.
935
+ def validate_attributes_for_import(failed_instances, column_names, array_of_attributes, validator, options)
936
+ # create instances for each of our column/value sets
937
+ arr = validations_array_for_column_names_and_attributes( column_names, array_of_attributes )
938
+
939
+ # keep track of the instance and the position it is currently at. if this fails
940
+ # validation we'll use the index to remove it from the array_of_attributes
941
+ arr.each_with_index do |hsh, i|
942
+ # utilize block initializer syntax to prevent failure when 'mass_assignment_sanitizer = :strict'
943
+ model = new do |m|
944
+ hsh.each_pair do |k, v|
945
+ if write_id_column_directly?(k)
946
+ m._write_attribute("id", v)
947
+ else
948
+ m[k] = v
949
+ end
950
+ end
951
+ end
952
+
953
+ next if validator.valid_model?(model)
954
+ raise(ActiveRecord::RecordInvalid, model) if options[:raise_error]
955
+
956
+ array_of_attributes[i] = nil
957
+ failure = model.dup
958
+ failure.errors.send(:initialize_dup, model.errors)
959
+ failed_instances << (options[:track_validation_failures] ? [i, failure] : failure )
960
+ end
961
+ array_of_attributes.compact!
962
+ end
963
+
964
+ # Imports the new and changed associations of the +models+ that were imported, with the
965
+ # :on_duplicate_key_update option that was passed in rather than the copy used for +models+.
966
+ def import_associations_of_imported_models(models, import_result, on_duplicate_key_update, options)
967
+ options[:on_duplicate_key_update] = on_duplicate_key_update unless on_duplicate_key_update.nil?
968
+ import_associations(imported_models(models, import_result, options), options.dup.merge(validate: false))
969
+ end
970
+
971
+ # ActiveRecord treats "id" as the primary key when writing attributes
972
+ # through id= or []=. When the primary key is composite or is another
973
+ # column, an "id" column has to be written directly instead.
974
+ def write_id_column_directly?(column)
975
+ column.to_s == "id" && primary_key != "id"
976
+ end
977
+
978
+ # Returns SQL comments for the annotations on the current scope, as
979
+ # ActiveRecord adds them to its queries, or nil if there are none.
980
+ def sql_comment_for_annotations(conn)
981
+ annotations = all.annotate_values
982
+ return if annotations.empty?
983
+
984
+ " #{annotations.map { |value| "/* #{conn.sanitize_as_sql_comment(value)} */" }.join(' ')}"
985
+ end
986
+
900
987
  def associated_options(options, association)
901
988
  return options unless options.key?(:recursive_on_duplicate_key_update)
902
989
 
@@ -905,9 +992,55 @@ class ActiveRecord::Base
905
992
  )
906
993
  end
907
994
 
995
+ # Returns the models that weren't rejected by validations.
996
+ def imported_models(models, import_result, options)
997
+ failed_instances = import_result.failed_instances
998
+ failed_instances = failed_instances.map(&:last) if options[:track_validation_failures]
999
+ models - failed_instances
1000
+ end
1001
+
1002
+ # Synchronizes +instances+ with the database like ActiveRecord::Base.synchronize,
1003
+ # but rather than discarding their changes, it assigns the database values
1004
+ # on top of them. Once the instances are marked clean, previous_changes then
1005
+ # reflects what the import changed, as it does without synchronizing.
1006
+ # Instances left without changes are marked clean right away. Returns the
1007
+ # instances that were synchronized.
1008
+ def synchronize_imported_instances(instances, keys)
1009
+ synchronize_instances(instances, keys) do |instance, fresh_attributes|
1010
+ attributes = instance.instance_variable_get(:@attributes)
1011
+ # the attributes of destroyed records are frozen
1012
+ attributes = attributes.deep_dup if attributes.frozen?
1013
+
1014
+ names = fresh_attributes.keys
1015
+ if names.all? { |name| attributes.key?(name) }
1016
+ names.each do |name|
1017
+ value = fresh_attributes.fetch_value(name)
1018
+ attributes.write_from_user(name, value) unless same_database_value?(attributes.fetch_value(name), value)
1019
+ end
1020
+ instance.instance_variable_set :@attributes, attributes
1021
+ instance.changes_applied unless instance.changed?
1022
+ else
1023
+ # the instance doesn't have all of the attributes, such as when it was
1024
+ # loaded with select, so take the record's attributes as they are
1025
+ instance.instance_variable_set :@attributes, fresh_attributes
1026
+ instance.clear_changes_information
1027
+ end
1028
+ end
1029
+ end
1030
+
1031
+ # Returns true if +database_value+, reloaded from the database, is the +value+ an instance
1032
+ # already has. Databases store times to the microsecond, so a time with more precision,
1033
+ # which ActiveRecord < 7.0 keeps in memory, comes back rounded or truncated.
1034
+ def same_database_value?(value, database_value)
1035
+ return true if value == database_value
1036
+ return false unless value.is_a?(Time) && database_value.is_a?(Time)
1037
+
1038
+ value.round(6) == database_value || value.floor(6) == database_value
1039
+ end
1040
+
908
1041
  def set_attributes_and_mark_clean(conn, models, import_result, timestamps, options)
909
1042
  return if models.nil?
910
- models -= import_result.failed_instances
1043
+ models = imported_models(models, import_result, options)
911
1044
 
912
1045
  # if ids were returned for all models we know all were updated
913
1046
  if models.size == import_result.ids.size
@@ -924,20 +1057,18 @@ class ActiveRecord::Base
924
1057
  deserialize_value = lambda do |column, value|
925
1058
  column = schema_columns_hash(conn)[column]
926
1059
  return value unless column
927
- if respond_to?(:type_caster)
928
- type = type_for_attribute(column.name)
929
- type.deserialize(value)
930
- elsif column.respond_to?(:type_cast_from_database)
931
- column.type_cast_from_database(value)
932
- else
933
- value
934
- end
1060
+ type = type_for_attribute(column.name)
1061
+ type.deserialize(value)
935
1062
  end
936
1063
 
937
1064
  set_value = lambda do |model, column, value|
938
1065
  val = deserialize_value.call(column, value)
939
1066
  if model.attribute_names.include?(column)
940
- model.send("#{column}=", val)
1067
+ if write_id_column_directly?(column)
1068
+ model._write_attribute(column, val)
1069
+ else
1070
+ model.send("#{column}=", val)
1071
+ end
941
1072
  else
942
1073
  attributes = attributes_builder.build_from_database(model.attributes.merge(column => val))
943
1074
  model.instance_variable_set(:@attributes, attributes)
@@ -962,14 +1093,7 @@ class ActiveRecord::Base
962
1093
  end
963
1094
 
964
1095
  models.each do |model|
965
- if model.respond_to?(:changes_applied) # Rails 4.1.8 and higher
966
- model.changes_internally_applied if model.respond_to?(:changes_internally_applied) # legacy behavior for Rails 5.1
967
- model.changes_applied
968
- elsif model.respond_to?(:clear_changes_information) # Rails 4.0 and higher
969
- model.clear_changes_information
970
- else # Rails 3.2
971
- model.instance_variable_get(:@changed_attributes).clear
972
- end
1096
+ model.changes_applied
973
1097
  model.instance_variable_set(:@new_record, false)
974
1098
  end
975
1099
  end
@@ -989,12 +1113,24 @@ class ActiveRecord::Base
989
1113
  association = association.target
990
1114
  next if association.blank? || model.public_send(column_name).present?
991
1115
 
992
- association_primary_key = Array(association_reflection.association_primary_key.tr("[]:", "").split(", "))[column_index]
1116
+ association_primary_key = association_primary_key_names(association_reflection)[column_index]
1117
+ next unless association_primary_key
1118
+
993
1119
  model.public_send("#{column_name}=", association.send(association_primary_key))
994
1120
  end
995
1121
  end
996
1122
  end
997
1123
 
1124
+ # Returns the names of the columns that a belongs_to association's foreign key refers to.
1125
+ # For a composite key, ActiveRecord 7.1+ returns an Array, while older versions return
1126
+ # its string form, e.g. "[:account_id, :id]".
1127
+ def association_primary_key_names(association_reflection)
1128
+ association_primary_key = association_reflection.association_primary_key
1129
+ return association_primary_key.map(&:to_s) if association_primary_key.is_a?(Array)
1130
+
1131
+ association_primary_key.tr("[]:", "").split(", ")
1132
+ end
1133
+
998
1134
  def import_associations(models, options)
999
1135
  # now, for all the dirty associations, collect them into a new set of models, then recurse.
1000
1136
  # notes:
@@ -1020,8 +1156,45 @@ class ActiveRecord::Base
1020
1156
  end
1021
1157
  end
1022
1158
 
1159
+ # Returns the columns the database populates itself, such as with a default
1160
+ # function, when importing new +models+. Like ActiveRecord's own inserts, the
1161
+ # database populates these for models that don't assign a value, instead of
1162
+ # inserting NULL. The primary key is handled separately.
1163
+ def database_populated_column_names(conn, column_names, models)
1164
+ return [] if models.any?(&:persisted?)
1165
+
1166
+ columns_hash = schema_columns_hash(conn)
1167
+ primary_keys = Array(primary_key).map(&:to_s)
1168
+
1169
+ column_names.select do |name|
1170
+ column = columns_hash[name.to_s]
1171
+ next false if column.nil? || primary_keys.include?(name.to_s)
1172
+
1173
+ column.respond_to?(:auto_populated?) ? column.auto_populated? : column.default_function
1174
+ end
1175
+ end
1176
+
1177
+ # Returns the primary key columns the database populates itself, such as a uuid
1178
+ # or a key with a default function, rather than from the model's sequence.
1179
+ def database_populated_primary_keys(conn)
1180
+ Array(primary_key).select do |name|
1181
+ column = schema_columns_hash(conn)[name]
1182
+ column && (column.type == :uuid || (column.default_function && sequence_name.blank?))
1183
+ end
1184
+ end
1185
+
1186
+ # A model of another class, such as an STI subclass with its own enum, can give
1187
+ # a column a different type than this class, which serializes the imported values.
1188
+ # Convert the model's value to this class's type through its database value.
1189
+ def value_from_model_type(model, name, value)
1190
+ name = column_name_for_import(name)
1191
+ model_type = model.class.type_for_attribute(name)
1192
+ type = type_for_attribute(name)
1193
+ model_type.equal?(type) ? value : type.deserialize(model_type.serialize(value))
1194
+ end
1195
+
1023
1196
  def schema_columns_hash(conn)
1024
- if respond_to?(:ignored_columns) && ignored_columns.any?
1197
+ if ignored_columns.any?
1025
1198
  conn.schema_cache.columns_hash(table_name)
1026
1199
  else
1027
1200
  columns_hash
@@ -1046,18 +1219,25 @@ class ActiveRecord::Base
1046
1219
  # Wrap target in an array if not already
1047
1220
  association = Array(association.target)
1048
1221
 
1049
- changed_objects = association.select { |a| a.new_record? || a.changed? }
1222
+ foreign_key_columns = Array(association_reflection.inverse_of&.foreign_key || association_reflection.foreign_key)
1223
+ foreign_key_values = association_foreign_key_values(model, association_reflection)
1224
+
1225
+ # For polymorphic associations
1226
+ association_name = model.class.polymorphic_name
1227
+
1228
+ # Import new and changed records, and existing records that were assigned to
1229
+ # this model but aren't linked to it yet, as saving the model would
1230
+ changed_objects = association.select do |child|
1231
+ child.new_record? || child.changed? ||
1232
+ (!association_reflection.through_reflection? &&
1233
+ reassigned_to_parent?(child, foreign_key_columns, foreign_key_values, association_reflection.type, association_name))
1234
+ end
1235
+
1050
1236
  changed_objects.each do |child|
1051
- Array(association_reflection.inverse_of&.foreign_key || association_reflection.foreign_key).each_with_index do |column, index|
1052
- child.public_send("#{column}=", Array(model.id)[index])
1237
+ foreign_key_columns.each_with_index do |column, index|
1238
+ child.public_send("#{column}=", foreign_key_values[index])
1053
1239
  end
1054
1240
 
1055
- # For polymorphic associations
1056
- association_name = if model.class.respond_to?(:polymorphic_name)
1057
- model.class.polymorphic_name
1058
- else
1059
- model.class.base_class
1060
- end
1061
1241
  association_reflection.type.try do |type|
1062
1242
  child.public_send("#{type}=", association_name)
1063
1243
  end
@@ -1067,33 +1247,55 @@ class ActiveRecord::Base
1067
1247
  associated_objects_by_class
1068
1248
  end
1069
1249
 
1250
+ # Returns the values of +model+ that the foreign key of +association_reflection+
1251
+ # refers to: the columns named by the association's :primary_key option when
1252
+ # one is given (e.g. <tt>has_many :reviews, primary_key: :identifier</tt>),
1253
+ # and the model's id otherwise.
1254
+ def association_foreign_key_values(model, association_reflection)
1255
+ if association_reflection.options[:primary_key]
1256
+ Array(association_reflection.active_record_primary_key).map { |key| model.read_attribute(key) }
1257
+ else
1258
+ Array(model.id)
1259
+ end
1260
+ end
1261
+
1262
+ # Returns true if +child+, an existing record of a has_one or has_many association,
1263
+ # doesn't refer to the parent yet, such as when it was assigned to a new parent.
1264
+ def reassigned_to_parent?(child, foreign_key_columns, foreign_key_values, type_column, type_name)
1265
+ return false if child.new_record?
1266
+
1267
+ parent_values = foreign_key_columns.zip(foreign_key_values)
1268
+ parent_values << [type_column, type_name.to_s] if type_column.present?
1269
+ parent_values.any? { |column, value| child.has_attribute?(column) && child.read_attribute(column) != value }
1270
+ end
1271
+
1070
1272
  # Returns SQL the VALUES for an INSERT statement given the passed in +columns+
1071
- # and +array_of_attributes+.
1072
- def values_sql_for_columns_and_attributes(conn, columns, array_of_attributes) # :nodoc:
1273
+ # and +array_of_attributes+. Nil values for +default_column_names+ use the
1274
+ # database's default.
1275
+ def values_sql_for_columns_and_attributes(conn, columns, array_of_attributes, default_column_names = []) # :nodoc:
1073
1276
  array_of_attributes.map do |arr|
1277
+ if arr.length < columns.length
1278
+ raise ArgumentError, "Number of values (#{arr.length}) is less than number of columns (#{columns.length})"
1279
+ end
1280
+
1074
1281
  my_values = arr.each_with_index.map do |val, j|
1075
1282
  column = columns[j]
1076
1283
 
1077
1284
  # be sure to query sequence_name *last*, only if cheaper tests fail, because it's costly
1078
1285
  if val.nil? && Array(primary_key).first == column.name && !sequence_name.blank?
1079
1286
  conn.next_value_for_sequence(sequence_name)
1287
+ elsif val.nil? && default_column_names.include?(column.name)
1288
+ unless conn.supports_default_keyword_in_values?
1289
+ raise ArgumentError, "#{conn.adapter_name} doesn't support DEFAULT in VALUES, so the database can't populate " \
1290
+ "#{table_name}.#{column.name} for only the rows without a value. Import those rows separately so the column can be left out."
1291
+ end
1292
+ "DEFAULT"
1080
1293
  elsif val.respond_to?(:to_sql)
1081
1294
  "(#{val.to_sql})"
1082
1295
  elsif column
1083
- if respond_to?(:type_caster) # Rails 5.0 and higher
1084
- type = type_for_attribute(column.name)
1085
- val = !type.respond_to?(:subtype) && type.type == :boolean ? type.cast(val) : type.serialize(val)
1086
- conn.quote(val)
1087
- elsif column.respond_to?(:type_cast_from_user) # Rails 4.2
1088
- conn.quote(column.type_cast_from_user(val), column)
1089
- else # Rails 3.2, 4.0 and 4.1
1090
- if serialized_attributes.include?(column.name)
1091
- val = serialized_attributes[column.name].dump(val)
1092
- end
1093
- # Fixes #443 to support binary (i.e. bytea) columns on PG
1094
- val = column.type_cast(val) unless column.type && column.type.to_sym == :binary
1095
- conn.quote(val, column)
1096
- end
1296
+ type = type_for_attribute(column.name)
1297
+ val = !type.respond_to?(:subtype) && type.type == :boolean ? type.cast(val) : type.serialize(val)
1298
+ conn.quote(val)
1097
1299
  else
1098
1300
  raise ArgumentError, "Number of values (#{arr.length}) exceeds number of columns (#{columns.length})"
1099
1301
  end
@@ -1106,14 +1308,8 @@ class ActiveRecord::Base
1106
1308
  timestamp_columns = {}
1107
1309
  timestamps = {}
1108
1310
 
1109
- if respond_to?(:all_timestamp_attributes_in_model, true) # Rails 5.1 and higher
1110
- timestamp_columns[:create] = timestamp_attributes_for_create_in_model
1111
- timestamp_columns[:update] = timestamp_attributes_for_update_in_model
1112
- else
1113
- instance = allocate
1114
- timestamp_columns[:create] = instance.send(:timestamp_attributes_for_create_in_model)
1115
- timestamp_columns[:update] = instance.send(:timestamp_attributes_for_update_in_model)
1116
- end
1311
+ timestamp_columns[:create] = timestamp_attributes_for_create_in_model
1312
+ timestamp_columns[:update] = timestamp_attributes_for_update_in_model
1117
1313
 
1118
1314
  # use tz as set in ActiveRecord::Base
1119
1315
  default_timezone = if ActiveRecord.respond_to?(:default_timezone)
@@ -1146,6 +1342,17 @@ class ActiveRecord::Base
1146
1342
  timestamps
1147
1343
  end
1148
1344
 
1345
+ # Converts array-like arguments, such as a Relation or a Set, to arrays.
1346
+ def arrays_from_array_like_import_arguments(args) # :nodoc:
1347
+ args.map { |arg| arg.is_a?(Enumerable) && !arg.is_a?(Array) && !arg.is_a?(Hash) ? arg.to_a : arg }
1348
+ end
1349
+
1350
+ def invalid_import_arguments_message(args) # :nodoc:
1351
+ received = args.map { |arg| arg.is_a?(Array) && arg.any? ? "Array of #{arg.first.class}" : arg.class.name }
1352
+ "Invalid arguments! Expected an array of models, an array of hashes, or column names and an array " \
1353
+ "of values, but received #{received.empty? ? 'no arguments' : received.join(', ')}"
1354
+ end
1355
+
1149
1356
  # Returns an Array of Hashes for the passed in +column_names+ and +array_of_attributes+.
1150
1357
  def validations_array_for_column_names_and_attributes( column_names, array_of_attributes ) # :nodoc:
1151
1358
  array_of_attributes.map { |values| Hash[column_names.zip(values)] }