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 +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
|