activerecord-import 1.0.2 → 1.5.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.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yaml +113 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +74 -8
  5. data/.rubocop_todo.yml +6 -16
  6. data/Brewfile +3 -1
  7. data/CHANGELOG.md +115 -3
  8. data/Gemfile +12 -10
  9. data/LICENSE +21 -56
  10. data/README.markdown +71 -60
  11. data/Rakefile +2 -0
  12. data/activerecord-import.gemspec +6 -5
  13. data/benchmarks/benchmark.rb +10 -4
  14. data/benchmarks/lib/base.rb +4 -2
  15. data/benchmarks/lib/cli_parser.rb +4 -2
  16. data/benchmarks/lib/float.rb +2 -0
  17. data/benchmarks/lib/mysql2_benchmark.rb +2 -0
  18. data/benchmarks/lib/output_to_csv.rb +2 -0
  19. data/benchmarks/lib/output_to_html.rb +4 -2
  20. data/benchmarks/models/test_innodb.rb +2 -0
  21. data/benchmarks/models/test_memory.rb +2 -0
  22. data/benchmarks/models/test_myisam.rb +2 -0
  23. data/benchmarks/schema/{mysql_schema.rb → mysql2_schema.rb} +2 -0
  24. data/gemfiles/4.2.gemfile +2 -0
  25. data/gemfiles/5.0.gemfile +2 -0
  26. data/gemfiles/5.1.gemfile +2 -0
  27. data/gemfiles/5.2.gemfile +2 -0
  28. data/gemfiles/6.0.gemfile +4 -1
  29. data/gemfiles/6.1.gemfile +4 -1
  30. data/gemfiles/7.0.gemfile +4 -0
  31. data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +2 -0
  32. data/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +6 -4
  33. data/lib/activerecord-import/active_record/adapters/jdbcpostgresql_adapter.rb +2 -0
  34. data/lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb +2 -0
  35. data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +2 -0
  36. data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +2 -0
  37. data/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb +2 -0
  38. data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +2 -0
  39. data/lib/activerecord-import/adapters/abstract_adapter.rb +14 -5
  40. data/lib/activerecord-import/adapters/em_mysql2_adapter.rb +2 -0
  41. data/lib/activerecord-import/adapters/mysql2_adapter.rb +2 -0
  42. data/lib/activerecord-import/adapters/mysql_adapter.rb +33 -25
  43. data/lib/activerecord-import/adapters/postgresql_adapter.rb +69 -56
  44. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +39 -39
  45. data/lib/activerecord-import/base.rb +10 -2
  46. data/lib/activerecord-import/import.rb +143 -62
  47. data/lib/activerecord-import/mysql2.rb +2 -0
  48. data/lib/activerecord-import/postgresql.rb +2 -0
  49. data/lib/activerecord-import/sqlite3.rb +2 -0
  50. data/lib/activerecord-import/synchronize.rb +3 -1
  51. data/lib/activerecord-import/value_sets_parser.rb +5 -0
  52. data/lib/activerecord-import/version.rb +3 -1
  53. data/lib/activerecord-import.rb +3 -1
  54. data/test/adapters/jdbcmysql.rb +2 -0
  55. data/test/adapters/jdbcpostgresql.rb +2 -0
  56. data/test/adapters/jdbcsqlite3.rb +2 -0
  57. data/test/adapters/makara_postgis.rb +2 -0
  58. data/test/adapters/mysql2.rb +2 -0
  59. data/test/adapters/mysql2_makara.rb +2 -0
  60. data/test/adapters/mysql2spatial.rb +2 -0
  61. data/test/adapters/postgis.rb +2 -0
  62. data/test/adapters/postgresql.rb +2 -0
  63. data/test/adapters/postgresql_makara.rb +2 -0
  64. data/test/adapters/seamless_database_pool.rb +2 -0
  65. data/test/adapters/spatialite.rb +2 -0
  66. data/test/adapters/sqlite3.rb +2 -0
  67. data/test/{travis → github}/database.yml +3 -1
  68. data/test/import_test.rb +93 -2
  69. data/test/jdbcmysql/import_test.rb +5 -3
  70. data/test/jdbcpostgresql/import_test.rb +4 -2
  71. data/test/jdbcsqlite3/import_test.rb +4 -2
  72. data/test/makara_postgis/import_test.rb +4 -2
  73. data/test/models/account.rb +2 -0
  74. data/test/models/alarm.rb +2 -0
  75. data/test/models/animal.rb +8 -0
  76. data/test/models/bike_maker.rb +3 -0
  77. data/test/models/book.rb +2 -0
  78. data/test/models/car.rb +2 -0
  79. data/test/models/card.rb +5 -0
  80. data/test/models/chapter.rb +2 -0
  81. data/test/models/customer.rb +8 -0
  82. data/test/models/deck.rb +8 -0
  83. data/test/models/dictionary.rb +2 -0
  84. data/test/models/discount.rb +2 -0
  85. data/test/models/end_note.rb +2 -0
  86. data/test/models/group.rb +2 -0
  87. data/test/models/order.rb +8 -0
  88. data/test/models/playing_card.rb +4 -0
  89. data/test/models/promotion.rb +2 -0
  90. data/test/models/question.rb +2 -0
  91. data/test/models/rule.rb +2 -0
  92. data/test/models/tag.rb +3 -0
  93. data/test/models/tag_alias.rb +5 -0
  94. data/test/models/topic.rb +7 -0
  95. data/test/models/user.rb +2 -0
  96. data/test/models/user_token.rb +2 -0
  97. data/test/models/vendor.rb +2 -0
  98. data/test/models/widget.rb +2 -0
  99. data/test/mysql2/import_test.rb +5 -3
  100. data/test/mysql2_makara/import_test.rb +5 -3
  101. data/test/mysqlspatial2/import_test.rb +5 -3
  102. data/test/postgis/import_test.rb +4 -2
  103. data/test/postgresql/import_test.rb +4 -2
  104. data/test/schema/generic_schema.rb +34 -0
  105. data/test/schema/jdbcpostgresql_schema.rb +3 -1
  106. data/test/schema/mysql2_schema.rb +2 -0
  107. data/test/schema/postgis_schema.rb +3 -1
  108. data/test/schema/postgresql_schema.rb +16 -0
  109. data/test/schema/sqlite3_schema.rb +2 -0
  110. data/test/schema/version.rb +2 -0
  111. data/test/sqlite3/import_test.rb +4 -2
  112. data/test/support/active_support/test_case_extensions.rb +2 -0
  113. data/test/support/assertions.rb +2 -0
  114. data/test/support/factories.rb +2 -0
  115. data/test/support/generate.rb +4 -2
  116. data/test/support/mysql/import_examples.rb +2 -1
  117. data/test/support/postgresql/import_examples.rb +96 -2
  118. data/test/support/shared_examples/on_duplicate_key_ignore.rb +2 -0
  119. data/test/support/shared_examples/on_duplicate_key_update.rb +50 -9
  120. data/test/support/shared_examples/recursive_import.rb +32 -1
  121. data/test/support/sqlite3/import_examples.rb +2 -1
  122. data/test/synchronize_test.rb +2 -0
  123. data/test/test_helper.rb +30 -5
  124. data/test/value_sets_bytes_parser_test.rb +3 -1
  125. data/test/value_sets_records_parser_test.rb +3 -1
  126. metadata +27 -16
  127. data/.travis.yml +0 -70
  128. data/gemfiles/3.2.gemfile +0 -2
  129. data/gemfiles/4.0.gemfile +0 -2
  130. data/gemfiles/4.1.gemfile +0 -2
@@ -1,18 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "ostruct"
2
4
 
3
5
  module ActiveRecord::Import::ConnectionAdapters; end
4
6
 
5
- module ActiveRecord::Import #:nodoc:
7
+ module ActiveRecord::Import # :nodoc:
6
8
  Result = Struct.new(:failed_instances, :num_inserts, :ids, :results)
7
9
 
8
- module ImportSupport #:nodoc:
9
- def supports_import? #:nodoc:
10
+ module ImportSupport # :nodoc:
11
+ def supports_import? # :nodoc:
10
12
  true
11
13
  end
12
14
  end
13
15
 
14
- module OnDuplicateKeyUpdateSupport #:nodoc:
15
- def supports_on_duplicate_key_update? #:nodoc:
16
+ module OnDuplicateKeyUpdateSupport # :nodoc:
17
+ def supports_on_duplicate_key_update? # :nodoc:
16
18
  true
17
19
  end
18
20
  end
@@ -34,7 +36,7 @@ module ActiveRecord::Import #:nodoc:
34
36
  @validate_callbacks = klass._validate_callbacks.dup
35
37
 
36
38
  @validate_callbacks.each_with_index do |callback, i|
37
- filter = callback.raw_filter
39
+ filter = callback.respond_to?(:raw_filter) ? callback.raw_filter : callback.filter
38
40
  next unless filter.class.name =~ /Validations::PresenceValidator/ ||
39
41
  (!@options[:validate_uniqueness] &&
40
42
  filter.is_a?(ActiveRecord::Validations::UniquenessValidator))
@@ -49,13 +51,13 @@ module ActiveRecord::Import #:nodoc:
49
51
  associations = klass.reflect_on_all_associations(:belongs_to)
50
52
  associations.each do |assoc|
51
53
  if (index = attrs.index(assoc.name))
52
- key = assoc.foreign_key.to_sym
54
+ key = assoc.foreign_key.is_a?(Array) ? assoc.foreign_key.map(&:to_sym) : assoc.foreign_key.to_sym
53
55
  attrs[index] = key unless attrs.include?(key)
54
56
  end
55
57
  end
56
58
  end
57
59
 
58
- filter.instance_variable_set(:@attributes, attrs)
60
+ filter.instance_variable_set(:@attributes, attrs.flatten)
59
61
 
60
62
  if @validate_callbacks.respond_to?(:chain, true)
61
63
  @validate_callbacks.send(:chain).tap do |chain|
@@ -71,7 +73,7 @@ module ActiveRecord::Import #:nodoc:
71
73
  end
72
74
 
73
75
  def valid_model?(model)
74
- init_validations(model.class) unless model.class == @validator_class
76
+ init_validations(model.class) unless model.instance_of?(@validator_class)
75
77
 
76
78
  validation_context = @options[:validate_with_context]
77
79
  validation_context ||= (model.new_record? ? :create : :update)
@@ -83,7 +85,11 @@ module ActiveRecord::Import #:nodoc:
83
85
 
84
86
  model.run_callbacks(:validation) do
85
87
  if defined?(ActiveSupport::Callbacks::Filters::Environment) # ActiveRecord >= 4.1
86
- runner = @validate_callbacks.compile
88
+ runner = if @validate_callbacks.method(:compile).arity == 0
89
+ @validate_callbacks.compile
90
+ else # ActiveRecord >= 7.1
91
+ @validate_callbacks.compile(nil)
92
+ end
87
93
  env = ActiveSupport::Callbacks::Filters::Environment.new(model, false, nil)
88
94
  if runner.respond_to?(:call) # ActiveRecord < 5.1
89
95
  runner.call(env)
@@ -163,7 +169,7 @@ class ActiveRecord::Associations::CollectionAssociation
163
169
  m.public_send "#{reflection.type}=", owner.class.name if reflection.type
164
170
  end
165
171
 
166
- return model_klass.bulk_import column_names, models, options
172
+ model_klass.bulk_import column_names, models, options
167
173
 
168
174
  # supports array of hash objects
169
175
  elsif args.last.is_a?( Array ) && args.last.first.is_a?(Hash)
@@ -202,11 +208,11 @@ class ActiveRecord::Associations::CollectionAssociation
202
208
  end
203
209
  end
204
210
 
205
- return model_klass.bulk_import column_names, array_of_attributes, options
211
+ model_klass.bulk_import column_names, array_of_attributes, options
206
212
 
207
213
  # supports empty array
208
214
  elsif args.last.is_a?( Array ) && args.last.empty?
209
- return ActiveRecord::Import::Result.new([], 0, [])
215
+ ActiveRecord::Import::Result.new([], 0, [])
210
216
 
211
217
  # supports 2-element array and array
212
218
  elsif args.size == 2 && args.first.is_a?( Array ) && args.last.is_a?( Array )
@@ -237,7 +243,7 @@ class ActiveRecord::Associations::CollectionAssociation
237
243
  end
238
244
  end
239
245
 
240
- return model_klass.bulk_import column_names, array_of_attributes, options
246
+ model_klass.bulk_import column_names, array_of_attributes, options
241
247
  else
242
248
  raise ArgumentError, "Invalid arguments!"
243
249
  end
@@ -245,16 +251,17 @@ class ActiveRecord::Associations::CollectionAssociation
245
251
  alias import bulk_import unless respond_to? :import
246
252
  end
247
253
 
254
+ module ActiveRecord::Import::Connection
255
+ def establish_connection(args = nil)
256
+ conn = super(args)
257
+ ActiveRecord::Import.load_from_connection_pool connection_pool
258
+ conn
259
+ end
260
+ end
261
+
248
262
  class ActiveRecord::Base
249
263
  class << self
250
- def establish_connection_with_activerecord_import(*args)
251
- conn = establish_connection_without_activerecord_import(*args)
252
- ActiveRecord::Import.load_from_connection_pool connection_pool
253
- conn
254
- end
255
-
256
- alias establish_connection_without_activerecord_import establish_connection
257
- alias establish_connection establish_connection_with_activerecord_import
264
+ prepend ActiveRecord::Import::Connection
258
265
 
259
266
  # Returns true if the current database connection adapter
260
267
  # supports import functionality, otherwise returns false.
@@ -546,7 +553,7 @@ class ActiveRecord::Base
546
553
  alias import! bulk_import! unless ActiveRecord::Base.respond_to? :import!
547
554
 
548
555
  def import_helper( *args )
549
- options = { validate: true, timestamps: true }
556
+ options = { model: self, validate: true, timestamps: true, track_validation_failures: false }
550
557
  options.merge!( args.pop ) if args.last.is_a? Hash
551
558
  # making sure that current model's primary key is used
552
559
  options[:primary_key] = primary_key
@@ -571,7 +578,7 @@ class ActiveRecord::Base
571
578
 
572
579
  if models.first.id.nil?
573
580
  Array(primary_key).each do |c|
574
- if column_names.include?(c) && columns_hash[c].type == :uuid
581
+ if column_names.include?(c) && schema_columns_hash[c].type == :uuid
575
582
  column_names.delete(c)
576
583
  end
577
584
  end
@@ -581,7 +588,7 @@ class ActiveRecord::Base
581
588
  if respond_to?(:timestamp_attributes_for_update, true)
582
589
  send(:timestamp_attributes_for_update).map(&:to_sym)
583
590
  else
584
- new.send(:timestamp_attributes_for_update_in_model)
591
+ allocate.send(:timestamp_attributes_for_update_in_model)
585
592
  end
586
593
  end
587
594
 
@@ -630,7 +637,7 @@ class ActiveRecord::Base
630
637
  end
631
638
  # supports empty array
632
639
  elsif args.last.is_a?( Array ) && args.last.empty?
633
- return ActiveRecord::Import::Result.new([], 0, [])
640
+ return ActiveRecord::Import::Result.new([], 0, [], [])
634
641
  # supports 2-element array and array
635
642
  elsif args.size == 2 && args.first.is_a?( Array ) && args.last.is_a?( Array )
636
643
 
@@ -694,7 +701,11 @@ class ActiveRecord::Base
694
701
  return_obj = if is_validating
695
702
  import_with_validations( column_names, array_of_attributes, options ) do |failed_instances|
696
703
  if models
697
- models.each { |m| failed_instances << m if m.errors.any? }
704
+ models.each_with_index do |m, i|
705
+ next unless m.errors.any?
706
+
707
+ failed_instances << (options[:track_validation_failures] ? [i, m] : m)
708
+ end
698
709
  else
699
710
  # create instances for each of our column/value sets
700
711
  arr = validations_array_for_column_names_and_attributes( column_names, array_of_attributes )
@@ -702,14 +713,18 @@ class ActiveRecord::Base
702
713
  # keep track of the instance and the position it is currently at. if this fails
703
714
  # validation we'll use the index to remove it from the array_of_attributes
704
715
  arr.each_with_index do |hsh, i|
705
- model = new
706
- hsh.each_pair { |k, v| model[k] = v }
716
+ # utilize block initializer syntax to prevent failure when 'mass_assignment_sanitizer = :strict'
717
+ model = new do |m|
718
+ hsh.each_pair { |k, v| m[k] = v }
719
+ end
720
+
707
721
  next if validator.valid_model?(model)
708
722
  raise(ActiveRecord::RecordInvalid, model) if options[:raise_error]
723
+
709
724
  array_of_attributes[i] = nil
710
725
  failure = model.dup
711
726
  failure.errors.send(:initialize_dup, model.errors)
712
- failed_instances << failure
727
+ failed_instances << (options[:track_validation_failures] ? [i, failure] : failure )
713
728
  end
714
729
  array_of_attributes.compact!
715
730
  end
@@ -729,7 +744,10 @@ class ActiveRecord::Base
729
744
  set_attributes_and_mark_clean(models, return_obj, timestamps, options)
730
745
 
731
746
  # if there are auto-save associations on the models we imported that are new, import them as well
732
- import_associations(models, options.dup) if options[:recursive]
747
+ if options[:recursive]
748
+ options[:on_duplicate_key_update] = on_duplicate_key_update unless on_duplicate_key_update.nil?
749
+ import_associations(models, options.dup.merge(validate: false))
750
+ end
733
751
  end
734
752
 
735
753
  return_obj
@@ -764,27 +782,29 @@ class ActiveRecord::Base
764
782
  def import_without_validations_or_callbacks( column_names, array_of_attributes, options = {} )
765
783
  return ActiveRecord::Import::Result.new([], 0, [], []) if array_of_attributes.empty?
766
784
 
767
- column_names = column_names.map(&:to_sym)
785
+ column_names = column_names.map do |name|
786
+ original_name = attribute_alias?(name) ? attribute_alias(name) : name
787
+ original_name.to_sym
788
+ end
768
789
  scope_columns, scope_values = scope_attributes.to_a.transpose
769
790
 
770
791
  unless scope_columns.blank?
771
792
  scope_columns.zip(scope_values).each do |name, value|
772
793
  name_as_sym = name.to_sym
773
- next if column_names.include?(name_as_sym)
774
-
775
- is_sti = (name_as_sym == inheritance_column.to_sym && self < base_class)
776
- value = Array(value).first if is_sti
777
-
794
+ next if column_names.include?(name_as_sym) || name_as_sym == inheritance_column.to_sym
778
795
  column_names << name_as_sym
779
796
  array_of_attributes.each { |attrs| attrs << value }
780
797
  end
781
798
  end
782
799
 
783
- columns = column_names.each_with_index.map do |name, i|
784
- column = columns_hash[name.to_s]
800
+ if finder_needs_type_condition? && !column_names.include?(inheritance_column.to_sym)
801
+ column_names << inheritance_column.to_sym
802
+ array_of_attributes.each { |attrs| attrs << sti_name }
803
+ end
785
804
 
805
+ columns = column_names.each_with_index.map do |name, i|
806
+ column = schema_columns_hash[name.to_s]
786
807
  raise ActiveRecord::Import::MissingColumnError.new(name.to_s, i) if column.nil?
787
-
788
808
  column
789
809
  end
790
810
 
@@ -800,17 +820,29 @@ class ActiveRecord::Base
800
820
  if supports_import?
801
821
  # generate the sql
802
822
  post_sql_statements = connection.post_sql_statements( quoted_table_name, options )
823
+ import_size = values_sql.size
824
+
825
+ batch_size = options[:batch_size] || import_size
826
+ run_proc = options[:batch_size].to_i.positive? && options[:batch_progress].respond_to?( :call )
827
+ progress_proc = options[:batch_progress]
828
+ current_batch = 0
829
+ batches = (import_size / batch_size.to_f).ceil
803
830
 
804
- batch_size = options[:batch_size] || values_sql.size
805
831
  values_sql.each_slice(batch_size) do |batch_values|
832
+ batch_started_at = Time.now.to_i
833
+
806
834
  # perform the inserts
807
835
  result = connection.insert_many( [insert_sql, post_sql_statements].flatten,
808
836
  batch_values,
809
837
  options,
810
- "#{model_name} Create Many Without Validations Or Callbacks" )
838
+ "#{model_name} Create Many" )
839
+
811
840
  number_inserted += result.num_inserts
812
841
  ids += result.ids
813
842
  results += result.results
843
+ current_batch += 1
844
+
845
+ progress_proc.call(import_size, batches, current_batch, Time.now.to_i - batch_started_at) if run_proc
814
846
  end
815
847
  else
816
848
  transaction(requires_new: true) do
@@ -836,22 +868,46 @@ class ActiveRecord::Base
836
868
  model.id = id
837
869
 
838
870
  timestamps.each do |attr, value|
839
- model.send(attr + "=", value)
871
+ model.send("#{attr}=", value) if model.send(attr).nil?
840
872
  end
841
873
  end
842
874
  end
843
875
 
844
- if models.size == import_result.results.size
845
- columns = Array(options[:returning])
846
- single_column = "#{columns.first}=" if columns.size == 1
847
- import_result.results.each_with_index do |result, index|
876
+ deserialize_value = lambda do |column, value|
877
+ column = schema_columns_hash[column]
878
+ return value unless column
879
+ if respond_to?(:type_caster)
880
+ type = type_for_attribute(column.name)
881
+ type.deserialize(value)
882
+ elsif column.respond_to?(:type_cast_from_database)
883
+ column.type_cast_from_database(value)
884
+ else
885
+ value
886
+ end
887
+ end
888
+
889
+ set_value = lambda do |model, column, value|
890
+ val = deserialize_value.call(column, value)
891
+ if model.attribute_names.include?(column)
892
+ model.send("#{column}=", val)
893
+ else
894
+ attributes = attributes_builder.build_from_database(model.attributes.merge(column => val))
895
+ model.instance_variable_set(:@attributes, attributes)
896
+ end
897
+ end
898
+
899
+ columns = Array(options[:returning_columns])
900
+ results = Array(import_result.results)
901
+ if models.size == results.size
902
+ single_column = columns.first if columns.size == 1
903
+ results.each_with_index do |result, index|
848
904
  model = models[index]
849
905
 
850
906
  if single_column
851
- model.send(single_column, result)
907
+ set_value.call(model, single_column, result)
852
908
  else
853
909
  columns.each_with_index do |column, col_index|
854
- model.send("#{column}=", result[col_index])
910
+ set_value.call(model, column, result[col_index])
855
911
  end
856
912
  end
857
913
  end
@@ -872,16 +928,22 @@ class ActiveRecord::Base
872
928
 
873
929
  # Sync belongs_to association ids with foreign key field
874
930
  def load_association_ids(model)
931
+ changed_columns = model.changed
875
932
  association_reflections = model.class.reflect_on_all_associations(:belongs_to)
876
933
  association_reflections.each do |association_reflection|
877
- column_name = association_reflection.foreign_key
878
934
  next if association_reflection.options[:polymorphic]
879
- association = model.association(association_reflection.name)
880
- association = association.target
881
- next if association.blank? || model.public_send(column_name).present?
882
935
 
883
- association_primary_key = association_reflection.association_primary_key
884
- model.public_send("#{column_name}=", association.send(association_primary_key))
936
+ column_names = Array(association_reflection.foreign_key).map(&:to_s)
937
+ column_names.each_with_index do |column_name, column_index|
938
+ next if changed_columns.include?(column_name)
939
+
940
+ association = model.association(association_reflection.name)
941
+ association = association.target
942
+ next if association.blank? || model.public_send(column_name).present?
943
+
944
+ association_primary_key = Array(association_reflection.association_primary_key)[column_index]
945
+ model.public_send("#{column_name}=", association.send(association_primary_key))
946
+ end
885
947
  end
886
948
  end
887
949
 
@@ -894,8 +956,9 @@ class ActiveRecord::Base
894
956
  associated_objects_by_class = {}
895
957
  models.each { |model| find_associated_objects_for_import(associated_objects_by_class, model) }
896
958
 
897
- # :on_duplicate_key_update and :returning not supported for associations
898
- options.delete(:on_duplicate_key_update)
959
+ # :on_duplicate_key_update only supported for all fields
960
+ options.delete(:on_duplicate_key_update) unless options[:on_duplicate_key_update] == :all
961
+ # :returning not supported for associations
899
962
  options.delete(:returning)
900
963
 
901
964
  associated_objects_by_class.each_value do |associations|
@@ -905,6 +968,14 @@ class ActiveRecord::Base
905
968
  end
906
969
  end
907
970
 
971
+ def schema_columns_hash
972
+ @schema_columns_hash ||= if respond_to?(:ignored_columns) && ignored_columns.any?
973
+ connection.schema_cache.columns_hash(table_name)
974
+ else
975
+ columns_hash
976
+ end
977
+ end
978
+
908
979
  # We are eventually going to call Class.import <objects> so we build up a hash
909
980
  # of class => objects to import.
910
981
  def find_associated_objects_for_import(associated_objects_by_class, model)
@@ -927,8 +998,13 @@ class ActiveRecord::Base
927
998
  changed_objects.each do |child|
928
999
  child.public_send("#{association_reflection.foreign_key}=", model.id)
929
1000
  # For polymorphic associations
1001
+ association_name = if model.class.respond_to?(:polymorphic_name)
1002
+ model.class.polymorphic_name
1003
+ else
1004
+ model.class.base_class
1005
+ end
930
1006
  association_reflection.type.try do |type|
931
- child.public_send("#{type}=", model.class.base_class.name)
1007
+ child.public_send("#{type}=", association_name)
932
1008
  end
933
1009
  end
934
1010
  associated_objects_by_class[model.class.name][association_reflection.name].concat changed_objects
@@ -955,7 +1031,7 @@ class ActiveRecord::Base
955
1031
  elsif column
956
1032
  if respond_to?(:type_caster) # Rails 5.0 and higher
957
1033
  type = type_for_attribute(column.name)
958
- val = type.type == :boolean ? type.cast(val) : type.serialize(val)
1034
+ val = !type.respond_to?(:subtype) && type.type == :boolean ? type.cast(val) : type.serialize(val)
959
1035
  connection_memo.quote(val)
960
1036
  elsif column.respond_to?(:type_cast_from_user) # Rails 4.2
961
1037
  connection_memo.quote(column.type_cast_from_user(val), column)
@@ -964,7 +1040,7 @@ class ActiveRecord::Base
964
1040
  val = serialized_attributes[column.name].dump(val)
965
1041
  end
966
1042
  # Fixes #443 to support binary (i.e. bytea) columns on PG
967
- val = column.type_cast(val) unless column.type.to_sym == :binary
1043
+ val = column.type_cast(val) unless column.type && column.type.to_sym == :binary
968
1044
  connection_memo.quote(val, column)
969
1045
  end
970
1046
  else
@@ -983,13 +1059,18 @@ class ActiveRecord::Base
983
1059
  timestamp_columns[:create] = timestamp_attributes_for_create_in_model
984
1060
  timestamp_columns[:update] = timestamp_attributes_for_update_in_model
985
1061
  else
986
- instance = new
1062
+ instance = allocate
987
1063
  timestamp_columns[:create] = instance.send(:timestamp_attributes_for_create_in_model)
988
1064
  timestamp_columns[:update] = instance.send(:timestamp_attributes_for_update_in_model)
989
1065
  end
990
1066
 
991
1067
  # use tz as set in ActiveRecord::Base
992
- timestamp = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
1068
+ default_timezone = if ActiveRecord.respond_to?(:default_timezone)
1069
+ ActiveRecord.default_timezone
1070
+ else
1071
+ ActiveRecord::Base.default_timezone
1072
+ end
1073
+ timestamp = default_timezone == :utc ? Time.now.utc : Time.now
993
1074
 
994
1075
  [:create, :update].each do |action|
995
1076
  timestamp_columns[action].each do |column|
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  warn <<-MSG
2
4
  [DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
3
5
  is deprecated. Update to autorequire using 'require "activerecord-import"'. See
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  warn <<-MSG
2
4
  [DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
3
5
  is deprecated. Update to autorequire using 'require "activerecord-import"'. See
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  warn <<-MSG
2
4
  [DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
3
5
  is deprecated. Update to autorequire using 'require "activerecord-import"'. See
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord # :nodoc:
2
4
  class Base # :nodoc:
3
5
  # Synchronizes the passed in ActiveRecord instances with data
@@ -39,7 +41,7 @@ module ActiveRecord # :nodoc:
39
41
 
40
42
  next unless matched_instance
41
43
 
42
- instance.send :clear_association_cache
44
+ instance.instance_variable_set :@association_cache, {}
43
45
  instance.send :clear_aggregation_cache if instance.respond_to?(:clear_aggregation_cache, true)
44
46
  instance.instance_variable_set :@attributes, matched_instance.instance_variable_get(:@attributes)
45
47
 
@@ -1,6 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/array'
4
+
1
5
  module ActiveRecord::Import
2
6
  class ValueSetTooLargeError < StandardError
3
7
  attr_reader :size
8
+
4
9
  def initialize(msg = "Value set exceeds max size", size = 0)
5
10
  @size = size
6
11
  super(msg)
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Import
3
- VERSION = "1.0.2".freeze
5
+ VERSION = "1.5.0"
4
6
  end
5
7
  end
@@ -1,4 +1,6 @@
1
- # rubocop:disable Style/FileName
1
+ # rubocop:disable Naming/FileName
2
+ # frozen_string_literal: true
3
+
2
4
  require "active_support/lazy_load_hooks"
3
5
 
4
6
  ActiveSupport.on_load(:active_record) do
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "jdbcmysql"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "jdbcpostgresql"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "jdbcsqlite3"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "postgis"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "mysql2"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "mysql2_makara"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "mysql2spatial"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "postgis"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "postgresql"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "postgresql"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "seamless_database_pool"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "spatialite"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV["ARE_DB"] = "sqlite3"
@@ -1,7 +1,8 @@
1
1
  common: &common
2
2
  username: root
3
- password:
3
+ password: root
4
4
  encoding: utf8
5
+ collation: utf8_general_ci
5
6
  host: localhost
6
7
  database: activerecord_import_test
7
8
 
@@ -37,6 +38,7 @@ oracle:
37
38
  postgresql: &postgresql
38
39
  <<: *common
39
40
  username: postgres
41
+ password: postgres
40
42
  adapter: postgresql
41
43
  min_messages: warning
42
44