activerecord-import 0.21.0 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d4441271287712d8e44b9cac793f2b8b8d315988
4
- data.tar.gz: ec27555ffefb8a071bcf3c54b354a8b56b6e68c2
3
+ metadata.gz: 89358f97c9fd1550a1d32d572db5f126d41b6b9c
4
+ data.tar.gz: e840ba026216f3e0bc60a91b52bb207a558324d0
5
5
  SHA512:
6
- metadata.gz: fbafbd6476a5f7c3681b26e85e3f4960fb69b6065697a173426392f91f1a89c188dd83b1b16c1639645ba33b8a1fbba97a91dea7e20186692c8ca3e33b9eaf41
7
- data.tar.gz: 54f3499be94b3c1b44a99128c39bb9f964e5bcb8da8549dbd66fe5d568a14f195d8b60f2c17a8dab9b5745ba7efb9db58950b433377aee510f4586e1a3cf2074
6
+ metadata.gz: de20a46627950ac06773a6b8215b5dd134a3dc0f574407e9980c69dcc45a19b08db80aee089812b83be356913bf8f343737fec8166a0a77356e5fb546ec80e8b
7
+ data.tar.gz: d1de87a630f697453cfbba9cb83e64b061c157a7d65affc271bfced0e4088afbc0870dcba0dfbc2c2ca1bc8854b137abcb0e8114de0471acb823ba9692df8f1c
@@ -1,3 +1,15 @@
1
+ ## Changes in 0.22.0
2
+
3
+ ### New Features
4
+
5
+ * Add support for importing hashes thru a has many association. Thanks
6
+ to @jkowens via \#483.
7
+
8
+ ### Fixes
9
+
10
+ * Fix validation logic for recursive import. Thanks to @eric-simonton-sama, @jkowens via
11
+ \#489.
12
+
1
13
  ## Changes in 0.21.0
2
14
 
3
15
  ### New Features
@@ -1,19 +1,5 @@
1
1
  # rubocop:disable Style/FileName
2
2
 
3
3
  ActiveSupport.on_load(:active_record) do
4
- class ActiveRecord::Base
5
- class << self
6
- def establish_connection_with_activerecord_import(*args)
7
- conn = establish_connection_without_activerecord_import(*args)
8
- if !ActiveRecord.const_defined?(:Import) || !ActiveRecord::Import.respond_to?(:load_from_connection_pool)
9
- require "activerecord-import/base"
10
- end
11
-
12
- ActiveRecord::Import.load_from_connection_pool connection_pool
13
- conn
14
- end
15
- alias establish_connection_without_activerecord_import establish_connection
16
- alias establish_connection establish_connection_with_activerecord_import
17
- end
18
- end
4
+ require "activerecord-import/base"
19
5
  end
@@ -197,7 +197,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
197
197
  current_version >= MIN_VERSION_FOR_UPSERT
198
198
  end
199
199
 
200
- def support_setting_primary_key_of_imported_objects?
200
+ def supports_setting_primary_key_of_imported_objects?
201
201
  true
202
202
  end
203
203
  end
@@ -18,12 +18,9 @@ module ActiveRecord::Import
18
18
 
19
19
  # Loads the import functionality for a specific database adapter
20
20
  def self.require_adapter(adapter)
21
- require File.join(ADAPTER_PATH, "/abstract_adapter")
22
- begin
23
- require File.join(ADAPTER_PATH, "/#{base_adapter(adapter)}_adapter")
24
- rescue LoadError
25
- # fallback
26
- end
21
+ require File.join(ADAPTER_PATH, "/#{base_adapter(adapter)}_adapter")
22
+ rescue LoadError
23
+ # fallback
27
24
  end
28
25
 
29
26
  # Loads the import functionality for the passed in ActiveRecord connection
@@ -103,7 +103,7 @@ class ActiveRecord::Associations::CollectionAssociation
103
103
  if args.last.is_a?( Array ) && args.last.first.is_a?(ActiveRecord::Base)
104
104
  if args.length == 2
105
105
  models = args.last
106
- column_names = args.first
106
+ column_names = args.first.dup
107
107
  else
108
108
  models = args.first
109
109
  column_names = symbolized_column_names
@@ -120,6 +120,45 @@ class ActiveRecord::Associations::CollectionAssociation
120
120
 
121
121
  return model_klass.import column_names, models, options
122
122
 
123
+ # supports array of hash objects
124
+ elsif args.last.is_a?( Array ) && args.last.first.is_a?(Hash)
125
+ if args.length == 2
126
+ array_of_hashes = args.last
127
+ column_names = args.first.dup
128
+ allow_extra_hash_keys = true
129
+ else
130
+ array_of_hashes = args.first
131
+ column_names = array_of_hashes.first.keys
132
+ allow_extra_hash_keys = false
133
+ end
134
+
135
+ symbolized_column_names = column_names.map(&:to_sym)
136
+ unless symbolized_column_names.include?(symbolized_foreign_key)
137
+ column_names << symbolized_foreign_key
138
+ end
139
+
140
+ if reflection.type && !symbolized_column_names.include?(reflection.type.to_sym)
141
+ column_names << reflection.type.to_sym
142
+ end
143
+
144
+ array_of_attributes = array_of_hashes.map do |h|
145
+ error_message = model_klass.send(:validate_hash_import, h, symbolized_column_names, allow_extra_hash_keys)
146
+
147
+ raise ArgumentError, error_message if error_message
148
+
149
+ column_names.map do |key|
150
+ if key == symbolized_foreign_key
151
+ owner_primary_key_value
152
+ elsif reflection.type && key == reflection.type.to_sym
153
+ owner.class.name
154
+ else
155
+ h[key]
156
+ end
157
+ end
158
+ end
159
+
160
+ return model_klass.import column_names, array_of_attributes, options
161
+
123
162
  # supports empty array
124
163
  elsif args.last.is_a?( Array ) && args.last.empty?
125
164
  return ActiveRecord::Import::Result.new([], 0, [])
@@ -127,7 +166,12 @@ class ActiveRecord::Associations::CollectionAssociation
127
166
  # supports 2-element array and array
128
167
  elsif args.size == 2 && args.first.is_a?( Array ) && args.last.is_a?( Array )
129
168
  column_names, array_of_attributes = args
130
- symbolized_column_names = column_names.map(&:to_s)
169
+
170
+ # dup the passed args so we don't modify unintentionally
171
+ column_names = column_names.dup
172
+ array_of_attributes = array_of_attributes.map(&:dup)
173
+
174
+ symbolized_column_names = column_names.map(&:to_sym)
131
175
 
132
176
  if symbolized_column_names.include?(symbolized_foreign_key)
133
177
  index = symbolized_column_names.index(symbolized_foreign_key)
@@ -138,8 +182,14 @@ class ActiveRecord::Associations::CollectionAssociation
138
182
  end
139
183
 
140
184
  if reflection.type
141
- column_names << reflection.type
142
- array_of_attributes.each { |attrs| attrs << owner.class.name }
185
+ symbolized_type = reflection.type.to_sym
186
+ if symbolized_column_names.include?(symbolized_type)
187
+ index = symbolized_column_names.index(symbolized_type)
188
+ array_of_attributes.each { |attrs| attrs[index] = owner.class.name }
189
+ else
190
+ column_names << symbolized_type
191
+ array_of_attributes.each { |attrs| attrs << owner.class.name }
192
+ end
143
193
  end
144
194
 
145
195
  return model_klass.import column_names, array_of_attributes, options
@@ -151,6 +201,15 @@ end
151
201
 
152
202
  class ActiveRecord::Base
153
203
  class << self
204
+ def establish_connection_with_activerecord_import(*args)
205
+ conn = establish_connection_without_activerecord_import(*args)
206
+ ActiveRecord::Import.load_from_connection_pool connection_pool
207
+ conn
208
+ end
209
+
210
+ alias establish_connection_without_activerecord_import establish_connection
211
+ alias establish_connection establish_connection_with_activerecord_import
212
+
154
213
  # Returns true if the current database connection adapter
155
214
  # supports import functionality, otherwise returns false.
156
215
  def supports_import?(*args)
@@ -161,14 +220,14 @@ class ActiveRecord::Base
161
220
  # supports on duplicate key update functionality, otherwise
162
221
  # returns false.
163
222
  def supports_on_duplicate_key_update?
164
- connection.supports_on_duplicate_key_update?
223
+ connection.respond_to?(:supports_on_duplicate_key_update?) && connection.supports_on_duplicate_key_update?
165
224
  end
166
225
 
167
226
  # returns true if the current database connection adapter
168
227
  # supports setting the primary key of bulk imported models, otherwise
169
228
  # returns false
170
- def support_setting_primary_key_of_imported_objects?
171
- connection.respond_to?(:support_setting_primary_key_of_imported_objects?) && connection.support_setting_primary_key_of_imported_objects?
229
+ def supports_setting_primary_key_of_imported_objects?
230
+ connection.respond_to?(:supports_setting_primary_key_of_imported_objects?) && connection.supports_setting_primary_key_of_imported_objects?
172
231
  end
173
232
 
174
233
  # Imports a collection of values to the database.
@@ -454,7 +513,7 @@ class ActiveRecord::Base
454
513
  end
455
514
 
456
515
  array_of_attributes = models.map do |model|
457
- if support_setting_primary_key_of_imported_objects?
516
+ if supports_setting_primary_key_of_imported_objects?
458
517
  load_association_ids(model)
459
518
  end
460
519
 
@@ -533,7 +592,6 @@ class ActiveRecord::Base
533
592
  if models
534
593
  import_with_validations( column_names, array_of_attributes, options ) do |validator, failed|
535
594
  models.each_with_index do |model, i|
536
- model = model.dup if options[:recursive]
537
595
  next if validator.valid_model? model
538
596
  raise(ActiveRecord::RecordInvalid, model) if options[:raise_error]
539
597
  array_of_attributes[i] = nil
@@ -554,7 +612,7 @@ class ActiveRecord::Base
554
612
  return_obj.num_inserts = 0 if return_obj.num_inserts.nil?
555
613
 
556
614
  # if we have ids, then set the id on the models and mark the models as clean.
557
- if models && support_setting_primary_key_of_imported_objects?
615
+ if models && supports_setting_primary_key_of_imported_objects?
558
616
  set_attributes_and_mark_clean(models, return_obj, timestamps, options)
559
617
 
560
618
  # if there are auto-save associations on the models we imported that are new, import them as well
@@ -753,6 +811,7 @@ class ActiveRecord::Base
753
811
  # of class => objects to import.
754
812
  def find_associated_objects_for_import(associated_objects_by_class, model)
755
813
  associated_objects_by_class[model.class.name] ||= {}
814
+ return associated_objects_by_class unless model.id
756
815
 
757
816
  association_reflections =
758
817
  model.class.reflect_on_all_associations(:has_one) +
@@ -871,7 +930,7 @@ class ActiveRecord::Base
871
930
 
872
931
  if allow_extra_keys
873
932
  <<-EOS
874
- Hash key mis-match.
933
+ Hash key mismatch.
875
934
 
876
935
  When importing an array of hashes with provided columns_names, each hash must contain keys for all column_names.
877
936
 
@@ -882,7 +941,7 @@ Hash: #{hash}
882
941
  EOS
883
942
  else
884
943
  <<-EOS
885
- Hash key mis-match.
944
+ Hash key mismatch.
886
945
 
887
946
  When importing an array of hashes, all hashes must have the same keys.
888
947
  If you have records that are missing some values, we recommend you either set default values
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Import
3
- VERSION = "0.21.0".freeze
3
+ VERSION = "0.22.0".freeze
4
4
  end
5
5
  end
@@ -248,7 +248,7 @@ describe "#import" do
248
248
  end
249
249
 
250
250
  it "should set ids in valid models if adapter supports setting primary key of imported objects" do
251
- if ActiveRecord::Base.support_setting_primary_key_of_imported_objects?
251
+ if ActiveRecord::Base.supports_setting_primary_key_of_imported_objects?
252
252
  Topic.import (invalid_models + valid_models), validate: true
253
253
  assert_nil invalid_models[0].id
254
254
  assert_nil invalid_models[1].id
@@ -258,7 +258,7 @@ describe "#import" do
258
258
  end
259
259
 
260
260
  it "should set ActiveRecord timestamps in valid models if adapter supports setting primary key of imported objects" do
261
- if ActiveRecord::Base.support_setting_primary_key_of_imported_objects?
261
+ if ActiveRecord::Base.supports_setting_primary_key_of_imported_objects?
262
262
  Timecop.freeze(Time.at(0)) do
263
263
  Topic.import (invalid_models + valid_models), validate: true
264
264
  end
@@ -368,7 +368,7 @@ describe "#import" do
368
368
 
369
369
  it "doesn't reload any data (doesn't work)" do
370
370
  Topic.import new_topics, synchronize: new_topics
371
- if Topic.support_setting_primary_key_of_imported_objects?
371
+ if Topic.supports_setting_primary_key_of_imported_objects?
372
372
  assert new_topics.all?(&:persisted?), "Records should have been reloaded"
373
373
  else
374
374
  assert new_topics.all?(&:new_record?), "No record should have been reloaded"
@@ -589,6 +589,12 @@ describe "#import" do
589
589
 
590
590
  assert_equal [val1, val2], scope.map(&column).sort
591
591
  end
592
+
593
+ it "works importing array of hashes" do
594
+ scope.import [{ column => val1 }, { column => val2 }]
595
+
596
+ assert_equal [val1, val2], scope.map(&column).sort
597
+ end
592
598
  end
593
599
  end
594
600
  end
@@ -90,6 +90,19 @@ def should_support_recursive_import
90
90
  end
91
91
  end
92
92
 
93
+ # Models are only valid if all associations are valid
94
+ it "only imports models with valid associations" do
95
+ assert_difference "Topic.count", 2 do
96
+ assert_difference "Book.count", 4 do
97
+ assert_difference "Chapter.count", 12 do
98
+ assert_difference "EndNote.count", 16 do
99
+ Topic.import new_topics_with_invalid_chapter, recursive: true
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+
93
106
  it "skips validation of the associations if requested" do
94
107
  assert_difference "Chapter.count", +num_chapters do
95
108
  Topic.import new_topics_with_invalid_chapter, validate: false, recursive: true
@@ -132,22 +145,14 @@ def should_support_recursive_import
132
145
  end
133
146
  end
134
147
 
135
- # These models dont validate associated. So we expect that books and topics get inserted, but not chapters
136
- # Putting a transaction around everything wouldn't work, so if you want your chapters to prevent topics from
137
- # being created, you would need to have validates_associated in your models and insert with validation
138
148
  describe "all_or_none" do
139
- [Book, Topic, EndNote].each do |type|
149
+ [Book, Chapter, Topic, EndNote].each do |type|
140
150
  it "creates #{type}" do
141
- assert_difference "#{type}.count", send("num_#{type.to_s.downcase}s") do
151
+ assert_difference "#{type}.count", 0 do
142
152
  Topic.import new_topics_with_invalid_chapter, all_or_none: true, recursive: true
143
153
  end
144
154
  end
145
155
  end
146
- it "doesn't create chapters" do
147
- assert_difference "Chapter.count", 0 do
148
- Topic.import new_topics_with_invalid_chapter, all_or_none: true, recursive: true
149
- end
150
- end
151
156
  end
152
157
 
153
158
  # If adapter supports on_duplicate_key_update, it is only applied to top level models so that SQL with invalid
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-import
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.21.0
4
+ version: 0.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zach Dennis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-21 00:00:00.000000000 Z
11
+ date: 2018-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord