activerecord-import 0.27.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (129) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yaml +107 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +74 -8
  5. data/Brewfile +3 -1
  6. data/CHANGELOG.md +175 -2
  7. data/Gemfile +13 -9
  8. data/LICENSE +21 -56
  9. data/README.markdown +525 -21
  10. data/Rakefile +2 -0
  11. data/activerecord-import.gemspec +6 -5
  12. data/benchmarks/benchmark.rb +7 -1
  13. data/benchmarks/lib/base.rb +2 -0
  14. data/benchmarks/lib/cli_parser.rb +3 -1
  15. data/benchmarks/lib/float.rb +2 -0
  16. data/benchmarks/lib/mysql2_benchmark.rb +2 -0
  17. data/benchmarks/lib/output_to_csv.rb +2 -0
  18. data/benchmarks/lib/output_to_html.rb +4 -2
  19. data/benchmarks/models/test_innodb.rb +2 -0
  20. data/benchmarks/models/test_memory.rb +2 -0
  21. data/benchmarks/models/test_myisam.rb +2 -0
  22. data/benchmarks/schema/{mysql_schema.rb → mysql2_schema.rb} +2 -0
  23. data/gemfiles/4.2.gemfile +2 -0
  24. data/gemfiles/5.0.gemfile +2 -0
  25. data/gemfiles/5.1.gemfile +2 -0
  26. data/gemfiles/5.2.gemfile +2 -0
  27. data/gemfiles/6.0.gemfile +4 -0
  28. data/gemfiles/6.1.gemfile +4 -0
  29. data/gemfiles/7.0.gemfile +4 -0
  30. data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +2 -0
  31. data/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +6 -4
  32. data/lib/activerecord-import/active_record/adapters/jdbcpostgresql_adapter.rb +2 -0
  33. data/lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb +2 -0
  34. data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +2 -0
  35. data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +2 -0
  36. data/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb +2 -0
  37. data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +2 -0
  38. data/lib/activerecord-import/adapters/abstract_adapter.rb +9 -1
  39. data/lib/activerecord-import/adapters/em_mysql2_adapter.rb +2 -0
  40. data/lib/activerecord-import/adapters/mysql2_adapter.rb +2 -0
  41. data/lib/activerecord-import/adapters/mysql_adapter.rb +10 -11
  42. data/lib/activerecord-import/adapters/postgresql_adapter.rb +49 -38
  43. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +21 -25
  44. data/lib/activerecord-import/base.rb +11 -2
  45. data/lib/activerecord-import/import.rb +180 -78
  46. data/lib/activerecord-import/mysql2.rb +2 -0
  47. data/lib/activerecord-import/postgresql.rb +2 -0
  48. data/lib/activerecord-import/sqlite3.rb +2 -0
  49. data/lib/activerecord-import/synchronize.rb +4 -2
  50. data/lib/activerecord-import/value_sets_parser.rb +4 -0
  51. data/lib/activerecord-import/version.rb +3 -1
  52. data/lib/activerecord-import.rb +3 -1
  53. data/test/adapters/jdbcmysql.rb +2 -0
  54. data/test/adapters/jdbcpostgresql.rb +2 -0
  55. data/test/adapters/jdbcsqlite3.rb +2 -0
  56. data/test/adapters/makara_postgis.rb +2 -0
  57. data/test/adapters/mysql2.rb +2 -0
  58. data/test/adapters/mysql2_makara.rb +2 -0
  59. data/test/adapters/mysql2spatial.rb +2 -0
  60. data/test/adapters/postgis.rb +2 -0
  61. data/test/adapters/postgresql.rb +2 -0
  62. data/test/adapters/postgresql_makara.rb +2 -0
  63. data/test/adapters/seamless_database_pool.rb +2 -0
  64. data/test/adapters/spatialite.rb +2 -0
  65. data/test/adapters/sqlite3.rb +2 -0
  66. data/test/{travis → github}/database.yml +3 -1
  67. data/test/import_test.rb +138 -4
  68. data/test/jdbcmysql/import_test.rb +2 -0
  69. data/test/jdbcpostgresql/import_test.rb +2 -0
  70. data/test/jdbcsqlite3/import_test.rb +2 -0
  71. data/test/makara_postgis/import_test.rb +2 -0
  72. data/test/models/account.rb +2 -0
  73. data/test/models/alarm.rb +2 -0
  74. data/test/models/animal.rb +8 -0
  75. data/test/models/bike_maker.rb +2 -0
  76. data/test/models/book.rb +2 -0
  77. data/test/models/car.rb +2 -0
  78. data/test/models/card.rb +5 -0
  79. data/test/models/chapter.rb +2 -0
  80. data/test/models/customer.rb +8 -0
  81. data/test/models/deck.rb +8 -0
  82. data/test/models/dictionary.rb +2 -0
  83. data/test/models/discount.rb +2 -0
  84. data/test/models/end_note.rb +2 -0
  85. data/test/models/group.rb +2 -0
  86. data/test/models/order.rb +8 -0
  87. data/test/models/playing_card.rb +4 -0
  88. data/test/models/promotion.rb +2 -0
  89. data/test/models/question.rb +2 -0
  90. data/test/models/rule.rb +2 -0
  91. data/test/models/tag.rb +3 -0
  92. data/test/models/tag_alias.rb +5 -0
  93. data/test/models/topic.rb +2 -0
  94. data/test/models/user.rb +2 -0
  95. data/test/models/user_token.rb +3 -0
  96. data/test/models/vendor.rb +2 -0
  97. data/test/models/widget.rb +2 -0
  98. data/test/mysql2/import_test.rb +2 -0
  99. data/test/mysql2_makara/import_test.rb +2 -0
  100. data/test/mysqlspatial2/import_test.rb +2 -0
  101. data/test/postgis/import_test.rb +2 -0
  102. data/test/postgresql/import_test.rb +2 -0
  103. data/test/schema/generic_schema.rb +33 -0
  104. data/test/schema/jdbcpostgresql_schema.rb +2 -0
  105. data/test/schema/mysql2_schema.rb +2 -0
  106. data/test/schema/postgis_schema.rb +2 -0
  107. data/test/schema/postgresql_schema.rb +18 -0
  108. data/test/schema/sqlite3_schema.rb +2 -0
  109. data/test/schema/version.rb +2 -0
  110. data/test/sqlite3/import_test.rb +2 -0
  111. data/test/support/active_support/test_case_extensions.rb +2 -0
  112. data/test/support/assertions.rb +2 -0
  113. data/test/support/factories.rb +2 -0
  114. data/test/support/generate.rb +4 -2
  115. data/test/support/mysql/import_examples.rb +2 -1
  116. data/test/support/postgresql/import_examples.rb +115 -2
  117. data/test/support/shared_examples/on_duplicate_key_ignore.rb +2 -0
  118. data/test/support/shared_examples/on_duplicate_key_update.rb +32 -0
  119. data/test/support/shared_examples/recursive_import.rb +61 -1
  120. data/test/support/sqlite3/import_examples.rb +4 -16
  121. data/test/synchronize_test.rb +2 -0
  122. data/test/test_helper.rb +27 -2
  123. data/test/value_sets_bytes_parser_test.rb +2 -0
  124. data/test/value_sets_records_parser_test.rb +2 -0
  125. metadata +29 -16
  126. data/.travis.yml +0 -71
  127. data/gemfiles/3.2.gemfile +0 -2
  128. data/gemfiles/4.0.gemfile +0 -2
  129. data/gemfiles/4.1.gemfile +0 -2
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "ostruct"
2
4
 
3
5
  module ActiveRecord::Import::ConnectionAdapters; end
@@ -24,33 +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
- associations = model.class.reflect_on_all_associations(:belongs_to).map(&:name)
42
-
43
- model._validate_callbacks.each do |callback|
44
- filter = callback.raw_filter
45
- if (!@options[:validate_uniqueness] && filter.is_a?(ActiveRecord::Validations::UniquenessValidator)) ||
46
- (defined?(ActiveRecord::Validations::PresenceValidator) && filter.is_a?(ActiveRecord::Validations::PresenceValidator) && associations.include?(filter.attributes.first))
47
- validate_callbacks.delete(callback)
48
- end
49
- end
50
-
51
86
  model.run_callbacks(:validation) do
52
87
  if defined?(ActiveSupport::Callbacks::Filters::Environment) # ActiveRecord >= 4.1
53
- runner = validate_callbacks.compile
88
+ runner = @validate_callbacks.compile
54
89
  env = ActiveSupport::Callbacks::Filters::Environment.new(model, false, nil)
55
90
  if runner.respond_to?(:call) # ActiveRecord < 5.1
56
91
  runner.call(env)
@@ -69,10 +104,10 @@ module ActiveRecord::Import #:nodoc:
69
104
  runner.invoke_before(env)
70
105
  runner.invoke_after(env)
71
106
  end
72
- elsif validate_callbacks.method(:compile).arity == 0 # ActiveRecord = 4.0
73
- model.instance_eval validate_callbacks.compile
107
+ elsif @validate_callbacks.method(:compile).arity == 0 # ActiveRecord = 4.0
108
+ model.instance_eval @validate_callbacks.compile
74
109
  else # ActiveRecord 3.x
75
- model.instance_eval validate_callbacks.compile(nil, model)
110
+ model.instance_eval @validate_callbacks.compile(nil, model)
76
111
  end
77
112
  end
78
113
 
@@ -212,16 +247,17 @@ class ActiveRecord::Associations::CollectionAssociation
212
247
  alias import bulk_import unless respond_to? :import
213
248
  end
214
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
256
+ end
257
+
215
258
  class ActiveRecord::Base
216
259
  class << self
217
- def establish_connection_with_activerecord_import(*args)
218
- conn = establish_connection_without_activerecord_import(*args)
219
- ActiveRecord::Import.load_from_connection_pool connection_pool
220
- conn
221
- end
222
-
223
- alias establish_connection_without_activerecord_import establish_connection
224
- alias establish_connection establish_connection_with_activerecord_import
260
+ prepend ActiveRecord::Import::Connection
225
261
 
226
262
  # Returns true if the current database connection adapter
227
263
  # supports import functionality, otherwise returns false.
@@ -498,7 +534,7 @@ class ActiveRecord::Base
498
534
  import_helper(*args)
499
535
  end
500
536
  end
501
- alias import bulk_import unless respond_to? :import
537
+ alias import bulk_import unless ActiveRecord::Base.respond_to? :import
502
538
 
503
539
  # Imports a collection of values if all values are valid. Import fails at the
504
540
  # first encountered validation error and raises ActiveRecord::RecordInvalid
@@ -510,17 +546,17 @@ class ActiveRecord::Base
510
546
 
511
547
  bulk_import(*args, options)
512
548
  end
513
- alias import! bulk_import! unless respond_to? :import!
549
+ alias import! bulk_import! unless ActiveRecord::Base.respond_to? :import!
514
550
 
515
551
  def import_helper( *args )
516
- options = { validate: true, timestamps: true }
552
+ options = { model: self, validate: true, timestamps: true, track_validation_failures: false }
517
553
  options.merge!( args.pop ) if args.last.is_a? Hash
518
554
  # making sure that current model's primary key is used
519
555
  options[:primary_key] = primary_key
520
556
  options[:locking_column] = locking_column if attribute_names.include?(locking_column)
521
557
 
522
558
  is_validating = options[:validate_with_context].present? ? true : options[:validate]
523
- validator = ActiveRecord::Import::Validator.new(options)
559
+ validator = ActiveRecord::Import::Validator.new(self, options)
524
560
 
525
561
  # assume array of model objects
526
562
  if args.last.is_a?( Array ) && args.last.first.is_a?(ActiveRecord::Base)
@@ -544,13 +580,12 @@ class ActiveRecord::Base
544
580
  end
545
581
  end
546
582
 
547
- default_values = column_defaults
548
- stored_attrs = respond_to?(:stored_attributes) ? stored_attributes : {}
549
- serialized_attrs = if defined?(ActiveRecord::Type::Serialized)
550
- attrs = column_names.select { |c| type_for_attribute(c.to_s).class == ActiveRecord::Type::Serialized }
551
- Hash[attrs.map { |a| [a, nil] }]
552
- else
553
- 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
554
589
  end
555
590
 
556
591
  array_of_attributes = []
@@ -566,12 +601,12 @@ class ActiveRecord::Base
566
601
  end
567
602
 
568
603
  array_of_attributes << column_names.map do |name|
569
- if stored_attrs.key?(name.to_sym) ||
570
- serialized_attrs.key?(name) ||
571
- default_values.key?(name.to_s)
572
- model.read_attribute(name.to_s)
604
+ if model.persisted? &&
605
+ update_attrs && update_attrs.include?(name.to_sym) &&
606
+ !model.send("#{name}_changed?")
607
+ nil
573
608
  else
574
- model.read_attribute_before_type_cast(name.to_s)
609
+ model.read_attribute(name.to_s)
575
610
  end
576
611
  end
577
612
  end
@@ -598,7 +633,7 @@ class ActiveRecord::Base
598
633
  end
599
634
  # supports empty array
600
635
  elsif args.last.is_a?( Array ) && args.last.empty?
601
- return ActiveRecord::Import::Result.new([], 0, [])
636
+ return ActiveRecord::Import::Result.new([], 0, [], [])
602
637
  # supports 2-element array and array
603
638
  elsif args.size == 2 && args.first.is_a?( Array ) && args.last.is_a?( Array )
604
639
 
@@ -655,7 +690,7 @@ class ActiveRecord::Base
655
690
  timestamps = {}
656
691
 
657
692
  # record timestamps unless disabled in ActiveRecord::Base
658
- if record_timestamps && options.delete( :timestamps )
693
+ if record_timestamps && options[:timestamps]
659
694
  timestamps = add_special_rails_stamps column_names, array_of_attributes, options
660
695
  end
661
696
 
@@ -670,14 +705,18 @@ class ActiveRecord::Base
670
705
  # keep track of the instance and the position it is currently at. if this fails
671
706
  # validation we'll use the index to remove it from the array_of_attributes
672
707
  arr.each_with_index do |hsh, i|
673
- model = new
674
- hsh.each_pair { |k, v| model[k] = v }
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
+
675
713
  next if validator.valid_model?(model)
676
714
  raise(ActiveRecord::RecordInvalid, model) if options[:raise_error]
715
+
677
716
  array_of_attributes[i] = nil
678
717
  failure = model.dup
679
718
  failure.errors.send(:initialize_dup, model.errors)
680
- failed_instances << failure
719
+ failed_instances << (options[:track_validation_failures] ? [i, failure] : failure )
681
720
  end
682
721
  array_of_attributes.compact!
683
722
  end
@@ -697,7 +736,10 @@ class ActiveRecord::Base
697
736
  set_attributes_and_mark_clean(models, return_obj, timestamps, options)
698
737
 
699
738
  # if there are auto-save associations on the models we imported that are new, import them as well
700
- import_associations(models, options.dup) if options[:recursive]
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
701
743
  end
702
744
 
703
745
  return_obj
@@ -738,21 +780,22 @@ class ActiveRecord::Base
738
780
  unless scope_columns.blank?
739
781
  scope_columns.zip(scope_values).each do |name, value|
740
782
  name_as_sym = name.to_sym
741
- next if column_names.include?(name_as_sym)
742
-
743
- is_sti = (name_as_sym == inheritance_column.to_sym && self < base_class)
744
- value = Array(value).first if is_sti
745
-
783
+ next if column_names.include?(name_as_sym) || name_as_sym == inheritance_column.to_sym
746
784
  column_names << name_as_sym
747
785
  array_of_attributes.each { |attrs| attrs << value }
748
786
  end
749
787
  end
750
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
+
751
796
  columns = column_names.each_with_index.map do |name, i|
752
797
  column = columns_hash[name.to_s]
753
-
754
798
  raise ActiveRecord::Import::MissingColumnError.new(name.to_s, i) if column.nil?
755
-
756
799
  column
757
800
  end
758
801
 
@@ -768,17 +811,29 @@ class ActiveRecord::Base
768
811
  if supports_import?
769
812
  # generate the sql
770
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
771
821
 
772
- batch_size = options[:batch_size] || values_sql.size
773
822
  values_sql.each_slice(batch_size) do |batch_values|
823
+ batch_started_at = Time.now.to_i
824
+
774
825
  # perform the inserts
775
826
  result = connection.insert_many( [insert_sql, post_sql_statements].flatten,
776
827
  batch_values,
777
828
  options,
778
- "#{model_name} Create Many Without Validations Or Callbacks" )
829
+ "#{model_name} Create Many" )
830
+
779
831
  number_inserted += result.num_inserts
780
832
  ids += result.ids
781
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
782
837
  end
783
838
  else
784
839
  transaction(requires_new: true) do
@@ -804,29 +859,56 @@ class ActiveRecord::Base
804
859
  model.id = id
805
860
 
806
861
  timestamps.each do |attr, value|
807
- model.send(attr + "=", value)
862
+ model.send(attr + "=", value) if model.send(attr).nil?
808
863
  end
809
864
  end
810
865
  end
811
866
 
812
- if models.size == import_result.results.size
813
- columns = Array(options[:returning])
814
- single_column = "#{columns.first}=" if columns.size == 1
815
- import_result.results.each_with_index do |result, index|
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|
816
895
  model = models[index]
817
896
 
818
897
  if single_column
819
- model.send(single_column, result)
898
+ set_value.call(model, single_column, result)
820
899
  else
821
900
  columns.each_with_index do |column, col_index|
822
- model.send("#{column}=", result[col_index])
901
+ set_value.call(model, column, result[col_index])
823
902
  end
824
903
  end
825
904
  end
826
905
  end
827
906
 
828
907
  models.each do |model|
829
- if model.respond_to?(:clear_changes_information) # Rails 4.0 and higher
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
830
912
  model.clear_changes_information
831
913
  else # Rails 3.2
832
914
  model.instance_variable_get(:@changed_attributes).clear
@@ -837,16 +919,22 @@ class ActiveRecord::Base
837
919
 
838
920
  # Sync belongs_to association ids with foreign key field
839
921
  def load_association_ids(model)
922
+ changed_columns = model.changed
840
923
  association_reflections = model.class.reflect_on_all_associations(:belongs_to)
841
924
  association_reflections.each do |association_reflection|
842
- column_name = association_reflection.foreign_key
843
925
  next if association_reflection.options[:polymorphic]
844
- association = model.association(association_reflection.name)
845
- association = association.target
846
- next if association.blank? || model.public_send(column_name).present?
847
926
 
848
- association_primary_key = association_reflection.association_primary_key
849
- model.public_send("#{column_name}=", association.send(association_primary_key))
927
+ column_names = Array(association_reflection.foreign_key).map(&:to_s)
928
+ column_names.each_with_index do |column_name, column_index|
929
+ next if changed_columns.include?(column_name)
930
+
931
+ association = model.association(association_reflection.name)
932
+ association = association.target
933
+ next if association.blank? || model.public_send(column_name).present?
934
+
935
+ association_primary_key = Array(association_reflection.association_primary_key)[column_index]
936
+ model.public_send("#{column_name}=", association.send(association_primary_key))
937
+ end
850
938
  end
851
939
  end
852
940
 
@@ -859,8 +947,10 @@ class ActiveRecord::Base
859
947
  associated_objects_by_class = {}
860
948
  models.each { |model| find_associated_objects_for_import(associated_objects_by_class, model) }
861
949
 
862
- # :on_duplicate_key_update not supported for associations
863
- 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)
864
954
 
865
955
  associated_objects_by_class.each_value do |associations|
866
956
  associations.each_value do |associated_records|
@@ -891,8 +981,13 @@ class ActiveRecord::Base
891
981
  changed_objects.each do |child|
892
982
  child.public_send("#{association_reflection.foreign_key}=", model.id)
893
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
894
989
  association_reflection.type.try do |type|
895
- child.public_send("#{type}=", model.class.base_class.name)
990
+ child.public_send("#{type}=", association_name)
896
991
  end
897
992
  end
898
993
  associated_objects_by_class[model.class.name][association_reflection.name].concat changed_objects
@@ -919,7 +1014,7 @@ class ActiveRecord::Base
919
1014
  elsif column
920
1015
  if respond_to?(:type_caster) # Rails 5.0 and higher
921
1016
  type = type_for_attribute(column.name)
922
- 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)
923
1018
  connection_memo.quote(val)
924
1019
  elsif column.respond_to?(:type_cast_from_user) # Rails 4.2
925
1020
  connection_memo.quote(column.type_cast_from_user(val), column)
@@ -928,9 +1023,11 @@ class ActiveRecord::Base
928
1023
  val = serialized_attributes[column.name].dump(val)
929
1024
  end
930
1025
  # Fixes #443 to support binary (i.e. bytea) columns on PG
931
- 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
932
1027
  connection_memo.quote(val, column)
933
1028
  end
1029
+ else
1030
+ raise ArgumentError, "Number of values (#{arr.length}) exceeds number of columns (#{columns.length})"
934
1031
  end
935
1032
  end
936
1033
  "(#{my_values.join(',')})"
@@ -945,13 +1042,18 @@ class ActiveRecord::Base
945
1042
  timestamp_columns[:create] = timestamp_attributes_for_create_in_model
946
1043
  timestamp_columns[:update] = timestamp_attributes_for_update_in_model
947
1044
  else
948
- instance = new
1045
+ instance = allocate
949
1046
  timestamp_columns[:create] = instance.send(:timestamp_attributes_for_create_in_model)
950
1047
  timestamp_columns[:update] = instance.send(:timestamp_attributes_for_update_in_model)
951
1048
  end
952
1049
 
953
1050
  # use tz as set in ActiveRecord::Base
954
- timestamp = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
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
955
1057
 
956
1058
  [:create, :update].each do |action|
957
1059
  timestamp_columns[action].each do |column|
@@ -961,7 +1063,7 @@ class ActiveRecord::Base
961
1063
  index = column_names.index(column) || column_names.index(column.to_sym)
962
1064
  if index
963
1065
  # replace every instance of the array of attributes with our value
964
- array_of_attributes.each { |arr| arr[index] = timestamp if arr[index].nil? || action == :update }
1066
+ array_of_attributes.each { |arr| arr[index] = timestamp if arr[index].nil? }
965
1067
  else
966
1068
  column_names << column
967
1069
  array_of_attributes.each { |arr| arr << timestamp }
@@ -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,8 +41,8 @@ module ActiveRecord # :nodoc:
39
41
 
40
42
  next unless matched_instance
41
43
 
42
- instance.send :clear_aggregation_cache
43
- instance.send :clear_association_cache
44
+ instance.instance_variable_set :@association_cache, {}
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
 
46
48
  if instance.respond_to?(:clear_changes_information)
@@ -1,3 +1,7 @@
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
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Import
3
- VERSION = "0.27.0".freeze
5
+ VERSION = "1.4.1"
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