activerecord-import 0.21.0 → 0.22.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/lib/activerecord-import.rb +1 -15
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +1 -1
- data/lib/activerecord-import/base.rb +3 -6
- data/lib/activerecord-import/import.rb +71 -12
- data/lib/activerecord-import/version.rb +1 -1
- data/test/import_test.rb +9 -3
- data/test/support/shared_examples/recursive_import.rb +15 -10
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89358f97c9fd1550a1d32d572db5f126d41b6b9c
|
4
|
+
data.tar.gz: e840ba026216f3e0bc60a91b52bb207a558324d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de20a46627950ac06773a6b8215b5dd134a3dc0f574407e9980c69dcc45a19b08db80aee089812b83be356913bf8f343737fec8166a0a77356e5fb546ec80e8b
|
7
|
+
data.tar.gz: d1de87a630f697453cfbba9cb83e64b061c157a7d65affc271bfced0e4088afbc0870dcba0dfbc2c2ca1bc8854b137abcb0e8114de0471acb823ba9692df8f1c
|
data/CHANGELOG.md
CHANGED
@@ -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
|
data/lib/activerecord-import.rb
CHANGED
@@ -1,19 +1,5 @@
|
|
1
1
|
# rubocop:disable Style/FileName
|
2
2
|
|
3
3
|
ActiveSupport.on_load(:active_record) do
|
4
|
-
|
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
|
@@ -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, "
|
22
|
-
|
23
|
-
|
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
|
-
|
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
|
-
|
142
|
-
|
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
|
171
|
-
connection.respond_to?(:
|
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
|
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 &&
|
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
|
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
|
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
|
data/test/import_test.rb
CHANGED
@@ -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.
|
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.
|
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.
|
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",
|
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.
|
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:
|
11
|
+
date: 2018-01-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|