activerecord-import 0.15.0 → 0.16.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/.travis.yml +4 -1
- data/CHANGELOG.md +23 -0
- data/Gemfile +1 -0
- data/benchmarks/README +2 -2
- data/lib/activerecord-import/adapters/abstract_adapter.rb +4 -10
- data/lib/activerecord-import/adapters/mysql_adapter.rb +14 -5
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +21 -9
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +22 -6
- data/lib/activerecord-import/import.rb +56 -30
- data/lib/activerecord-import/version.rb +1 -1
- data/test/import_test.rb +115 -19
- data/test/models/alarm.rb +2 -0
- data/test/models/dictionary.rb +2 -0
- data/test/models/vendor.rb +7 -0
- data/test/models/widget.rb +16 -0
- data/test/postgis/import_test.rb +4 -0
- data/test/schema/generic_schema.rb +14 -2
- data/test/schema/jdbcpostgresql_schema.rb +1 -0
- data/test/schema/postgis_schema.rb +1 -0
- data/test/schema/postgresql_schema.rb +41 -0
- data/test/sqlite3/import_test.rb +15 -0
- data/test/support/factories.rb +1 -0
- data/test/support/mysql/import_examples.rb +1 -0
- data/test/support/postgresql/import_examples.rb +91 -0
- data/test/support/shared_examples/on_duplicate_key_ignore.rb +25 -0
- data/test/support/shared_examples/recursive_import.rb +25 -7
- data/test/test_helper.rb +2 -0
- metadata +17 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 64717eaa9cb0c49761ad29e6aa358c87e96b7166
|
4
|
+
data.tar.gz: 879ac9c11ee4a9a62988c97608ad195a1052893d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e06cfe24bc207291c4a4c8737f8c3d6309218ccde2ca3246089f45d7f36c85f6fa7a63abe5758934dd6c9e939422c40395880b7ba950e10db7b96fa38b071af
|
7
|
+
data.tar.gz: 6ce0fc279753ab340269eefa2d64be18c40d2bed279f3cda27ba4057cb428582a207c5637da5a7c5222cffa77d2a43a0030578c87ebe05f76c81ad098da3fa21
|
data/.travis.yml
CHANGED
@@ -27,10 +27,13 @@ matrix:
|
|
27
27
|
before_script:
|
28
28
|
- mysql -e 'create database activerecord_import_test;'
|
29
29
|
- psql -c 'create database activerecord_import_test;' -U postgres
|
30
|
-
- psql
|
30
|
+
- psql activerecord_import_test -c 'create extension if not exists hstore;' -U postgres
|
31
|
+
- psql -c 'create extension if not exists postgis;' -U postgres
|
32
|
+
- psql -c 'create extension if not exists "uuid-ossp";' -U postgres
|
31
33
|
- cp test/travis/database.yml test/database.yml
|
32
34
|
|
33
35
|
addons:
|
36
|
+
postgresql: "9.4"
|
34
37
|
apt:
|
35
38
|
sources:
|
36
39
|
- travis-ci/sqlite3
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,26 @@
|
|
1
|
+
## Changes in 0.16.0
|
2
|
+
|
3
|
+
### New Features
|
4
|
+
|
5
|
+
* Add partial index upsert support for PostgreSQL. Thanks to @luislew via \#305
|
6
|
+
* Add UUID primary key support for PostgreSQL. Thanks to @jkowens via
|
7
|
+
\#312
|
8
|
+
* Add store accessor support for JSON, JSON, and HSTORE data types.
|
9
|
+
Thanks to @jkowens via \#322
|
10
|
+
* Log warning if database does not support :on_duplicate_key_update.
|
11
|
+
Thanks to @jkowens vi \#324
|
12
|
+
* Add option :on_duplicate_key_ignore for MySQL and SQLite. Thanks to
|
13
|
+
@jkowens via \#326
|
14
|
+
|
15
|
+
### Fixes
|
16
|
+
|
17
|
+
* Fixes issue with recursive import using same primary key for all models.
|
18
|
+
Thanks to @chopraanmol1 via \#309
|
19
|
+
* Fixes issue importing from STI subclass with polymorphic associations.
|
20
|
+
Thanks to @JNajera via \#314
|
21
|
+
* Fixes issue setting returned IDs to wrong models when some fail validation. Also fixes issue with SQLite returning wrong IDs. Thanks to @mizukami234 via \#315
|
22
|
+
|
23
|
+
|
1
24
|
## Changes in 0.15.0
|
2
25
|
|
3
26
|
### New Features
|
data/Gemfile
CHANGED
data/benchmarks/README
CHANGED
@@ -15,10 +15,10 @@ See "ruby benchmark.rb -h" for the complete listing of options.
|
|
15
15
|
EXAMPLES
|
16
16
|
--------
|
17
17
|
To output to html format:
|
18
|
-
ruby benchmark.rb --adapter=
|
18
|
+
ruby benchmark.rb --adapter=mysql2 --to-html=results.html
|
19
19
|
|
20
20
|
To output to csv format:
|
21
|
-
ruby benchmark.rb --adapter=
|
21
|
+
ruby benchmark.rb --adapter=mysql2 --to-csv=results.csv
|
22
22
|
|
23
23
|
LIMITATIONS
|
24
24
|
-----------
|
@@ -23,7 +23,6 @@ module ActiveRecord::Import::AbstractAdapter
|
|
23
23
|
sql = []
|
24
24
|
sql << options[:pre_sql] if options[:pre_sql]
|
25
25
|
sql << options[:command] if options[:command]
|
26
|
-
sql << "IGNORE" if options[:ignore]
|
27
26
|
|
28
27
|
# add keywords like IGNORE or DELAYED
|
29
28
|
if options[:keywords].is_a?(Array)
|
@@ -45,15 +44,10 @@ module ActiveRecord::Import::AbstractAdapter
|
|
45
44
|
def post_sql_statements( table_name, options ) # :nodoc:
|
46
45
|
post_sql_statements = []
|
47
46
|
|
48
|
-
if supports_on_duplicate_key_update?
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
post_sql_statements << sql_for_on_duplicate_key_ignore( table_name, options[:on_duplicate_key_ignore] )
|
53
|
-
end
|
54
|
-
elsif options[:on_duplicate_key_update]
|
55
|
-
post_sql_statements << sql_for_on_duplicate_key_update( table_name, options[:on_duplicate_key_update] )
|
56
|
-
end
|
47
|
+
if supports_on_duplicate_key_update? && options[:on_duplicate_key_update]
|
48
|
+
post_sql_statements << sql_for_on_duplicate_key_update( table_name, options[:on_duplicate_key_update] )
|
49
|
+
elsif options[:on_duplicate_key_update]
|
50
|
+
logger.warn "Ignoring on_duplicate_key_update because it is not supported by the database."
|
57
51
|
end
|
58
52
|
|
59
53
|
# custom user post_sql
|
@@ -39,10 +39,13 @@ module ActiveRecord::Import::MysqlAdapter
|
|
39
39
|
value_sets = ::ActiveRecord::Import::ValueSetsBytesParser.parse(values,
|
40
40
|
reserved_bytes: sql_size,
|
41
41
|
max_bytes: max)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
42
|
+
|
43
|
+
transaction(requires_new: true) do
|
44
|
+
value_sets.each do |value_set|
|
45
|
+
number_of_inserts += 1
|
46
|
+
sql2insert = base_sql + value_set.join( ',' ) + post_sql
|
47
|
+
insert( sql2insert, *args )
|
48
|
+
end
|
46
49
|
end
|
47
50
|
end
|
48
51
|
|
@@ -60,6 +63,12 @@ module ActiveRecord::Import::MysqlAdapter
|
|
60
63
|
end
|
61
64
|
end
|
62
65
|
|
66
|
+
def pre_sql_statements( options)
|
67
|
+
sql = []
|
68
|
+
sql << "IGNORE" if options[:ignore] || options[:on_duplicate_key_ignore]
|
69
|
+
sql + super
|
70
|
+
end
|
71
|
+
|
63
72
|
# Add a column to be updated on duplicate key update
|
64
73
|
def add_column_for_on_duplicate_key_update( column, options = {} ) # :nodoc:
|
65
74
|
if options.include?(:on_duplicate_key_update)
|
@@ -68,7 +77,7 @@ module ActiveRecord::Import::MysqlAdapter
|
|
68
77
|
when Array then columns << column.to_sym unless columns.include?(column.to_sym)
|
69
78
|
when Hash then columns[column.to_sym] = column.to_sym
|
70
79
|
end
|
71
|
-
|
80
|
+
elsif !options[:ignore] && !options[:on_duplicate_key_ignore]
|
72
81
|
options[:on_duplicate_key_update] = [column.to_sym]
|
73
82
|
end
|
74
83
|
end
|
@@ -31,11 +31,24 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def post_sql_statements( table_name, options ) # :nodoc:
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
sql = []
|
35
|
+
|
36
|
+
if supports_on_duplicate_key_update?
|
37
|
+
# Options :recursive and :on_duplicate_key_ignore are mutually exclusive
|
38
|
+
if (options[:ignore] || options[:on_duplicate_key_ignore]) && !options[:on_duplicate_key_update] && !options[:recursive]
|
39
|
+
sql << sql_for_on_duplicate_key_ignore( table_name, options[:on_duplicate_key_ignore] )
|
40
|
+
end
|
41
|
+
elsif options[:on_duplicate_key_ignore] && !options[:on_duplicate_key_update]
|
42
|
+
logger.warn "Ignoring on_duplicate_key_ignore because it is not supported by the database."
|
43
|
+
end
|
44
|
+
|
45
|
+
sql += super(table_name, options)
|
46
|
+
|
47
|
+
unless options[:no_returning] || options[:primary_key].blank?
|
48
|
+
sql << "RETURNING #{options[:primary_key]}"
|
38
49
|
end
|
50
|
+
|
51
|
+
sql
|
39
52
|
end
|
40
53
|
|
41
54
|
# Add a column to be updated on duplicate key update
|
@@ -113,10 +126,13 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
113
126
|
def sql_for_conflict_target( args = {} )
|
114
127
|
constraint_name = args[:constraint_name]
|
115
128
|
conflict_target = args[:conflict_target]
|
129
|
+
index_predicate = args[:index_predicate]
|
116
130
|
if constraint_name.present?
|
117
131
|
"ON CONSTRAINT #{constraint_name} "
|
118
132
|
elsif conflict_target.present?
|
119
|
-
'(' << Array( conflict_target ).reject( &:empty? ).join( ', ' ) << ') '
|
133
|
+
'(' << Array( conflict_target ).reject( &:empty? ).join( ', ' ) << ') '.tap do |sql|
|
134
|
+
sql << "WHERE #{index_predicate} " if index_predicate
|
135
|
+
end
|
120
136
|
end
|
121
137
|
end
|
122
138
|
|
@@ -134,10 +150,6 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
134
150
|
current_version >= MIN_VERSION_FOR_UPSERT
|
135
151
|
end
|
136
152
|
|
137
|
-
def supports_on_duplicate_key_ignore?(current_version = postgresql_version)
|
138
|
-
supports_on_duplicate_key_update?(current_version)
|
139
|
-
end
|
140
|
-
|
141
153
|
def support_setting_primary_key_of_imported_objects?
|
142
154
|
true
|
143
155
|
end
|
@@ -30,21 +30,37 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
30
30
|
value_sets = ::ActiveRecord::Import::ValueSetsRecordsParser.parse(values,
|
31
31
|
max_records: SQLITE_LIMIT_COMPOUND_SELECT)
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
33
|
+
transaction(requires_new: true) do
|
34
|
+
value_sets.each do |value_set|
|
35
|
+
number_of_inserts += 1
|
36
|
+
sql2insert = base_sql + value_set.join( ',' ) + post_sql
|
37
|
+
last_insert_id = insert( sql2insert, *args )
|
38
|
+
first_insert_id = last_insert_id - affected_rows + 1
|
39
|
+
ids.concat((first_insert_id..last_insert_id).to_a)
|
40
|
+
end
|
39
41
|
end
|
40
42
|
|
41
43
|
[number_of_inserts, ids]
|
42
44
|
end
|
43
45
|
|
46
|
+
def pre_sql_statements( options)
|
47
|
+
sql = []
|
48
|
+
# Options :recursive and :on_duplicate_key_ignore are mutually exclusive
|
49
|
+
if (options[:ignore] || options[:on_duplicate_key_ignore]) && !options[:recursive]
|
50
|
+
sql << "OR IGNORE"
|
51
|
+
end
|
52
|
+
sql + super
|
53
|
+
end
|
54
|
+
|
44
55
|
def next_value_for_sequence(sequence_name)
|
45
56
|
%{nextval('#{sequence_name}')}
|
46
57
|
end
|
47
58
|
|
59
|
+
def affected_rows
|
60
|
+
result = execute('SELECT changes();')
|
61
|
+
result.first[0]
|
62
|
+
end
|
63
|
+
|
48
64
|
def support_setting_primary_key_of_imported_objects?
|
49
65
|
true
|
50
66
|
end
|
@@ -171,10 +171,11 @@ class ActiveRecord::Base
|
|
171
171
|
# == Options
|
172
172
|
# * +validate+ - true|false, tells import whether or not to use
|
173
173
|
# ActiveRecord validations. Validations are enforced by default.
|
174
|
-
# * +ignore+ - true|false,
|
175
|
-
#
|
176
|
-
#
|
177
|
-
#
|
174
|
+
# * +ignore+ - true|false, an alias for on_duplicate_key_ignore.
|
175
|
+
# * +on_duplicate_key_ignore+ - true|false, tells import to discard
|
176
|
+
# records that contain duplicate keys. For Postgres 9.5+ it adds
|
177
|
+
# ON CONFLICT DO NOTHING, for MySQL it uses INSERT IGNORE, and for
|
178
|
+
# SQLite it uses INSERT OR IGNORE. Cannot be enabled on a
|
178
179
|
# recursive import.
|
179
180
|
# * +on_duplicate_key_update+ - an Array or Hash, tells import to
|
180
181
|
# use MySQL's ON DUPLICATE KEY UPDATE or Postgres 9.5+ ON CONFLICT
|
@@ -246,7 +247,8 @@ class ActiveRecord::Base
|
|
246
247
|
# == On Duplicate Key Update (Postgres 9.5+)
|
247
248
|
#
|
248
249
|
# The :on_duplicate_key_update option can be an Array or a Hash with up to
|
249
|
-
#
|
250
|
+
# three attributes, :conflict_target (and optionally :index_predicate) or
|
251
|
+
# :constraint_name, and :columns.
|
250
252
|
#
|
251
253
|
# ==== Using an Array
|
252
254
|
#
|
@@ -260,10 +262,11 @@ class ActiveRecord::Base
|
|
260
262
|
#
|
261
263
|
# ==== Using a Hash
|
262
264
|
#
|
263
|
-
# The :
|
264
|
-
# :conflict_target
|
265
|
-
#
|
266
|
-
#
|
265
|
+
# The :on_duplicate_key_update option can be a hash with up to three
|
266
|
+
# attributes, :conflict_target (and optionally :index_predicate) or
|
267
|
+
# :constraint_name, and :columns. Unlike MySQL, Postgres requires the
|
268
|
+
# conflicting constraint to be explicitly specified. Using this option
|
269
|
+
# allows you to specify a constraint other than the primary key.
|
267
270
|
#
|
268
271
|
# ====== :conflict_target
|
269
272
|
#
|
@@ -273,7 +276,16 @@ class ActiveRecord::Base
|
|
273
276
|
# but it is the preferred method of identifying a constraint. It will
|
274
277
|
# default to the primary key. Below is an example:
|
275
278
|
#
|
276
|
-
# BlogPost.import columns, values, on_duplicate_key_update: { conflict_target: [:author_id, :slug], columns: [ :date_modified ] }
|
279
|
+
# BlogPost.import columns, values, on_duplicate_key_update: { conflict_target: [ :author_id, :slug ], columns: [ :date_modified ] }
|
280
|
+
#
|
281
|
+
# ====== :index_predicate
|
282
|
+
#
|
283
|
+
# The :index_predicate attribute optionally specifies a WHERE condition
|
284
|
+
# on :conflict_target, which is required for matching against partial
|
285
|
+
# indexes. This attribute is ignored if :constraint_name is included.
|
286
|
+
# Below is an example:
|
287
|
+
#
|
288
|
+
# BlogPost.import columns, values, on_duplicate_key_update: { conflict_target: [ :author_id, :slug ], index_predicate: 'status <> 0', columns: [ :date_modified ] }
|
277
289
|
#
|
278
290
|
# ====== :constraint_name
|
279
291
|
#
|
@@ -307,7 +319,7 @@ class ActiveRecord::Base
|
|
307
319
|
# This returns an object which responds to +failed_instances+ and +num_inserts+.
|
308
320
|
# * failed_instances - an array of objects that fails validation and were not committed to the database. An empty array if no validation is performed.
|
309
321
|
# * num_inserts - the number of insert statements it took to import the data
|
310
|
-
# * ids - the primary keys of the imported ids
|
322
|
+
# * ids - the primary keys of the imported ids if the adapter supports it, otherwise an empty array.
|
311
323
|
def import(*args)
|
312
324
|
if args.first.is_a?( Array ) && args.first.first.is_a?(ActiveRecord::Base)
|
313
325
|
options = {}
|
@@ -332,11 +344,13 @@ class ActiveRecord::Base
|
|
332
344
|
end
|
333
345
|
|
334
346
|
def import_helper( *args )
|
335
|
-
options = { validate: true, timestamps: true
|
347
|
+
options = { validate: true, timestamps: true }
|
336
348
|
options.merge!( args.pop ) if args.last.is_a? Hash
|
349
|
+
# making sure that current model's primary key is used
|
350
|
+
options[:primary_key] = primary_key
|
337
351
|
|
338
352
|
# Don't modify incoming arguments
|
339
|
-
if options[:on_duplicate_key_update]
|
353
|
+
if options[:on_duplicate_key_update] && options[:on_duplicate_key_update].duplicable?
|
340
354
|
options[:on_duplicate_key_update] = options[:on_duplicate_key_update].dup
|
341
355
|
end
|
342
356
|
|
@@ -347,17 +361,27 @@ class ActiveRecord::Base
|
|
347
361
|
if args.last.is_a?( Array ) && args.last.first.is_a?(ActiveRecord::Base)
|
348
362
|
if args.length == 2
|
349
363
|
models = args.last
|
350
|
-
column_names = args.first
|
364
|
+
column_names = args.first.dup
|
351
365
|
else
|
352
366
|
models = args.first
|
353
367
|
column_names = self.column_names.dup
|
354
368
|
end
|
355
369
|
|
370
|
+
if column_names.include?(primary_key) && columns_hash[primary_key].type == :uuid
|
371
|
+
column_names.delete(primary_key)
|
372
|
+
end
|
373
|
+
|
374
|
+
stored_attrs = respond_to?(:stored_attributes) ? stored_attributes : {}
|
375
|
+
|
356
376
|
array_of_attributes = models.map do |model|
|
357
377
|
# this next line breaks sqlite.so with a segmentation fault
|
358
378
|
# if model.new_record? || options[:on_duplicate_key_update]
|
359
379
|
column_names.map do |name|
|
360
|
-
|
380
|
+
if stored_attrs.any? && stored_attrs.key?(name.to_sym)
|
381
|
+
model.read_attribute(name.to_s)
|
382
|
+
else
|
383
|
+
model.read_attribute_before_type_cast(name.to_s)
|
384
|
+
end
|
361
385
|
end
|
362
386
|
# end
|
363
387
|
end
|
@@ -367,14 +391,14 @@ class ActiveRecord::Base
|
|
367
391
|
# supports 2-element array and array
|
368
392
|
elsif args.size == 2 && args.first.is_a?( Array ) && args.last.is_a?( Array )
|
369
393
|
column_names, array_of_attributes = args
|
394
|
+
|
395
|
+
# dup the passed args so we don't modify unintentionally
|
396
|
+
column_names = column_names.dup
|
370
397
|
array_of_attributes = array_of_attributes.map(&:dup)
|
371
398
|
else
|
372
399
|
raise ArgumentError, "Invalid arguments!"
|
373
400
|
end
|
374
401
|
|
375
|
-
# dup the passed in array so we don't modify it unintentionally
|
376
|
-
column_names = column_names.dup
|
377
|
-
|
378
402
|
# Force the primary key col into the insert if it's not
|
379
403
|
# on the list and we are using a sequence and stuff a nil
|
380
404
|
# value for it into each row so the sequencer will fire later
|
@@ -394,11 +418,7 @@ class ActiveRecord::Base
|
|
394
418
|
models.each_with_index do |model, i|
|
395
419
|
model = model.dup if options[:recursive]
|
396
420
|
next if model.valid?(options[:validate_with_context])
|
397
|
-
if options[:raise_error]
|
398
|
-
model.send(:raise_validation_error)
|
399
|
-
elsif options[:raise_error] # Rails 3.2, 4.0, 4.1 and 4.2
|
400
|
-
model.send(:raise_record_invalid)
|
401
|
-
end
|
421
|
+
raise(ActiveRecord::RecordInvalid, model) if options[:raise_error]
|
402
422
|
array_of_attributes[i] = nil
|
403
423
|
failed << model
|
404
424
|
end
|
@@ -418,7 +438,7 @@ class ActiveRecord::Base
|
|
418
438
|
return_obj.num_inserts = 0 if return_obj.num_inserts.nil?
|
419
439
|
|
420
440
|
# if we have ids, then set the id on the models and mark the models as clean.
|
421
|
-
if support_setting_primary_key_of_imported_objects?
|
441
|
+
if models && support_setting_primary_key_of_imported_objects?
|
422
442
|
set_ids_and_mark_clean(models, return_obj)
|
423
443
|
|
424
444
|
# if there are auto-save associations on the models we imported that are new, import them as well
|
@@ -502,7 +522,9 @@ class ActiveRecord::Base
|
|
502
522
|
end
|
503
523
|
|
504
524
|
columns_sql = "(#{column_names.map { |name| connection.quote_column_name(name) }.join(',')})"
|
505
|
-
|
525
|
+
pre_sql_statements = connection.pre_sql_statements( options )
|
526
|
+
insert_sql = ['INSERT', pre_sql_statements, "INTO #{quoted_table_name} #{columns_sql} VALUES "]
|
527
|
+
insert_sql = insert_sql.flatten.join(' ')
|
506
528
|
values_sql = values_sql_for_columns_and_attributes(columns, array_of_attributes)
|
507
529
|
|
508
530
|
number_inserted = 0
|
@@ -521,9 +543,11 @@ class ActiveRecord::Base
|
|
521
543
|
ids += result[1]
|
522
544
|
end
|
523
545
|
else
|
524
|
-
|
525
|
-
|
526
|
-
|
546
|
+
transaction(requires_new: true) do
|
547
|
+
values_sql.each do |values|
|
548
|
+
ids << connection.insert(insert_sql + values)
|
549
|
+
number_inserted += 1
|
550
|
+
end
|
527
551
|
end
|
528
552
|
end
|
529
553
|
[number_inserted, ids]
|
@@ -533,9 +557,10 @@ class ActiveRecord::Base
|
|
533
557
|
|
534
558
|
def set_ids_and_mark_clean(models, import_result)
|
535
559
|
return if models.nil?
|
560
|
+
models -= import_result.failed_instances
|
536
561
|
import_result.ids.each_with_index do |id, index|
|
537
562
|
model = models[index]
|
538
|
-
model.id = id
|
563
|
+
model.id = id
|
539
564
|
if model.respond_to?(:clear_changes_information) # Rails 4.0 and higher
|
540
565
|
model.clear_changes_information
|
541
566
|
else # Rails 3.2
|
@@ -550,6 +575,7 @@ class ActiveRecord::Base
|
|
550
575
|
# notes:
|
551
576
|
# does not handle associations that reference themselves
|
552
577
|
# should probably take a hash to associations to follow.
|
578
|
+
return if models.nil?
|
553
579
|
associated_objects_by_class = {}
|
554
580
|
models.each { |model| find_associated_objects_for_import(associated_objects_by_class, model) }
|
555
581
|
|
@@ -585,7 +611,7 @@ class ActiveRecord::Base
|
|
585
611
|
child.public_send("#{association_reflection.foreign_key}=", model.id)
|
586
612
|
# For polymorphic associations
|
587
613
|
association_reflection.type.try do |type|
|
588
|
-
child.public_send("#{type}=", model.class.name)
|
614
|
+
child.public_send("#{type}=", model.class.base_class.name)
|
589
615
|
end
|
590
616
|
end
|
591
617
|
associated_objects_by_class[model.class.name][association_reflection.name].concat changed_objects
|
data/test/import_test.rb
CHANGED
@@ -44,6 +44,25 @@ describe "#import" do
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
47
|
+
|
48
|
+
context "that have no primary key" do
|
49
|
+
it "should import models successfully" do
|
50
|
+
assert_difference "Rule.count", +3 do
|
51
|
+
Rule.import Build(3, :rules)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "with STI models" do
|
58
|
+
it "should import models successfully" do
|
59
|
+
dictionaries = [Dictionary.new(author_name: "Noah Webster", title: "Webster's Dictionary")]
|
60
|
+
|
61
|
+
assert_difference "Dictionary.count", +1 do
|
62
|
+
Dictionary.import dictionaries
|
63
|
+
end
|
64
|
+
assert_equal "Dictionary", Dictionary.first.type
|
65
|
+
end
|
47
66
|
end
|
48
67
|
|
49
68
|
context "with :validation option" do
|
@@ -51,6 +70,8 @@ describe "#import" do
|
|
51
70
|
let(:valid_values) { [["LDAP", "Jerry Carter"], ["Rails Recipes", "Chad Fowler"]] }
|
52
71
|
let(:valid_values_with_context) { [[1111, "Jerry Carter"], [2222, "Chad Fowler"]] }
|
53
72
|
let(:invalid_values) { [["The RSpec Book", ""], ["Agile+UX", ""]] }
|
73
|
+
let(:valid_models) { valid_values.map { |title, author_name| Topic.new(title: title, author_name: author_name) } }
|
74
|
+
let(:invalid_models) { invalid_values.map { |title, author_name| Topic.new(title: title, author_name: author_name) } }
|
54
75
|
|
55
76
|
context "with validation checks turned off" do
|
56
77
|
it "should import valid data" do
|
@@ -103,6 +124,16 @@ describe "#import" do
|
|
103
124
|
results.failed_instances.each { |e| assert_kind_of Topic, e }
|
104
125
|
end
|
105
126
|
|
127
|
+
it "should set ids in valid models if adapter supports setting primary key of imported objects" do
|
128
|
+
if ActiveRecord::Base.support_setting_primary_key_of_imported_objects?
|
129
|
+
Topic.import (invalid_models + valid_models), validate: true
|
130
|
+
assert_nil invalid_models[0].id
|
131
|
+
assert_nil invalid_models[1].id
|
132
|
+
assert_equal valid_models[0].id, Topic.all[0].id
|
133
|
+
assert_equal valid_models[1].id, Topic.all[1].id
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
106
137
|
it "should import valid data when mixed with invalid data" do
|
107
138
|
assert_difference "Topic.count", +2 do
|
108
139
|
Topic.import columns, valid_values + invalid_values, validate: true
|
@@ -112,6 +143,18 @@ describe "#import" do
|
|
112
143
|
end
|
113
144
|
end
|
114
145
|
|
146
|
+
context "without :validation option" do
|
147
|
+
let(:columns) { %w(title author_name) }
|
148
|
+
let(:invalid_values) { [["The RSpec Book", ""], ["Agile+UX", ""]] }
|
149
|
+
|
150
|
+
it "should not import invalid data" do
|
151
|
+
assert_no_difference "Topic.count" do
|
152
|
+
result = Topic.import columns, invalid_values
|
153
|
+
assert_equal 2, result.failed_instances.size
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
115
158
|
context "with :all_or_none option" do
|
116
159
|
let(:columns) { %w(title author_name) }
|
117
160
|
let(:valid_values) { [["LDAP", "Jerry Carter"], ["Rails Recipes", "Chad Fowler"]] }
|
@@ -281,6 +324,12 @@ describe "#import" do
|
|
281
324
|
Topic.import [:id, :author_name, :title], [[99, "Bob Jones", "Topic 99"]]
|
282
325
|
assert_equal 99, Topic.last.id
|
283
326
|
end
|
327
|
+
|
328
|
+
it "ignores the recursive option" do
|
329
|
+
assert_difference "Topic.count", +1 do
|
330
|
+
Topic.import [:author_name, :title], [["David Chelimsky", "The RSpec Book"]], recursive: true
|
331
|
+
end
|
332
|
+
end
|
284
333
|
end
|
285
334
|
|
286
335
|
context "ActiveRecord timestamps" do
|
@@ -485,43 +534,90 @@ describe "#import" do
|
|
485
534
|
end
|
486
535
|
|
487
536
|
describe "importing serialized fields" do
|
488
|
-
it "imports values for serialized fields" do
|
537
|
+
it "imports values for serialized Hash fields" do
|
489
538
|
assert_difference "Widget.unscoped.count", +1 do
|
490
539
|
Widget.import [:w_id, :data], [[1, { a: :b }]]
|
491
540
|
end
|
492
541
|
assert_equal({ a: :b }, Widget.find_by_w_id(1).data)
|
493
542
|
end
|
494
543
|
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
544
|
+
it "imports values for serialized fields" do
|
545
|
+
assert_difference "Widget.unscoped.count", +1 do
|
546
|
+
Widget.import [:w_id, :unspecified_data], [[1, { a: :b }]]
|
547
|
+
end
|
548
|
+
assert_equal({ a: :b }, Widget.find_by_w_id(1).unspecified_data)
|
549
|
+
end
|
550
|
+
|
551
|
+
it "imports values for custom coder" do
|
552
|
+
assert_difference "Widget.unscoped.count", +1 do
|
553
|
+
Widget.import [:w_id, :custom_data], [[1, { a: :b }]]
|
554
|
+
end
|
555
|
+
assert_equal({ a: :b }, Widget.find_by_w_id(1).custom_data)
|
556
|
+
end
|
557
|
+
|
558
|
+
let(:data) { { a: :b } }
|
559
|
+
it "imports values for serialized JSON fields" do
|
560
|
+
assert_difference "Widget.unscoped.count", +1 do
|
561
|
+
Widget.import [:w_id, :json_data], [[9, data]]
|
562
|
+
end
|
563
|
+
assert_equal(data.as_json, Widget.find_by_w_id(9).json_data)
|
564
|
+
end
|
565
|
+
|
566
|
+
context "with a store" do
|
567
|
+
it "imports serialized attributes set using accessors" do
|
568
|
+
vendors = [Vendor.new(name: 'Vendor 1', color: 'blue')]
|
569
|
+
assert_difference "Vendor.count", +1 do
|
570
|
+
Vendor.import vendors
|
500
571
|
end
|
501
|
-
assert_equal(
|
572
|
+
assert_equal('blue', Vendor.first.color)
|
502
573
|
end
|
503
574
|
end
|
504
575
|
end
|
505
576
|
|
506
577
|
describe "#import!" do
|
507
|
-
|
508
|
-
|
509
|
-
|
578
|
+
context "with an array of unsaved model instances" do
|
579
|
+
let(:topics) { Build(2, :topics) }
|
580
|
+
let(:invalid_topics) { Build(2, :invalid_topics) }
|
581
|
+
|
582
|
+
context "with invalid data" do
|
583
|
+
it "should raise ActiveRecord::RecordInvalid" do
|
584
|
+
assert_no_difference "Topic.count" do
|
585
|
+
assert_raise ActiveRecord::RecordInvalid do
|
586
|
+
Topic.import! invalid_topics
|
587
|
+
end
|
588
|
+
end
|
589
|
+
end
|
590
|
+
end
|
510
591
|
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
Topic.import! columns, invalid_values
|
592
|
+
context "with valid data" do
|
593
|
+
it "should import data" do
|
594
|
+
assert_difference "Topic.count", +2 do
|
595
|
+
Topic.import! topics
|
516
596
|
end
|
517
597
|
end
|
518
598
|
end
|
519
599
|
end
|
520
600
|
|
521
|
-
context "with
|
522
|
-
|
523
|
-
|
524
|
-
|
601
|
+
context "with array of columns and array of values" do
|
602
|
+
let(:columns) { %w(title author_name) }
|
603
|
+
let(:valid_values) { [["LDAP", "Jerry Carter"], ["Rails Recipes", "Chad Fowler"]] }
|
604
|
+
let(:invalid_values) { [["Rails Recipes", "Chad Fowler"], ["The RSpec Book", ""], ["Agile+UX", ""]] }
|
605
|
+
|
606
|
+
context "with invalid data" do
|
607
|
+
it "should raise ActiveRecord::RecordInvalid" do
|
608
|
+
assert_no_difference "Topic.count" do
|
609
|
+
assert_raise ActiveRecord::RecordInvalid do
|
610
|
+
Topic.import! columns, invalid_values
|
611
|
+
end
|
612
|
+
end
|
613
|
+
end
|
614
|
+
end
|
615
|
+
|
616
|
+
context "with valid data" do
|
617
|
+
it "should import data" do
|
618
|
+
assert_difference "Topic.count", +2 do
|
619
|
+
Topic.import! columns, valid_values
|
620
|
+
end
|
525
621
|
end
|
526
622
|
end
|
527
623
|
end
|
data/test/models/widget.rb
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
class CustomCoder
|
2
|
+
def load(value)
|
3
|
+
if value.nil?
|
4
|
+
{}
|
5
|
+
else
|
6
|
+
YAML.load(value)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def dump(value)
|
11
|
+
YAML.dump(value)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
1
15
|
class Widget < ActiveRecord::Base
|
2
16
|
self.primary_key = :w_id
|
3
17
|
|
@@ -5,4 +19,6 @@ class Widget < ActiveRecord::Base
|
|
5
19
|
|
6
20
|
serialize :data, Hash
|
7
21
|
serialize :json_data, JSON
|
22
|
+
serialize :unspecified_data
|
23
|
+
serialize :custom_data, CustomCoder.new
|
8
24
|
end
|
data/test/postgis/import_test.rb
CHANGED
@@ -2,3 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
|
2
2
|
require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
|
3
3
|
|
4
4
|
should_support_postgresql_import_functionality
|
5
|
+
|
6
|
+
if ActiveRecord::Base.connection.supports_on_duplicate_key_update?
|
7
|
+
should_support_postgresql_upsert_functionality
|
8
|
+
end
|
@@ -64,6 +64,7 @@ ActiveRecord::Schema.define do
|
|
64
64
|
t.integer :topic_id
|
65
65
|
t.boolean :for_sale, default: true
|
66
66
|
t.integer :status, default: 0
|
67
|
+
t.string :type
|
67
68
|
end
|
68
69
|
|
69
70
|
create_table :chapters, force: :cascade do |t|
|
@@ -73,7 +74,7 @@ ActiveRecord::Schema.define do
|
|
73
74
|
t.datetime :updated_at
|
74
75
|
end
|
75
76
|
|
76
|
-
create_table :end_notes, force: :cascade do |t|
|
77
|
+
create_table :end_notes, primary_key: :end_note_id, force: :cascade do |t|
|
77
78
|
t.string :note
|
78
79
|
t.integer :book_id, null: false
|
79
80
|
t.datetime :created_at
|
@@ -115,6 +116,8 @@ ActiveRecord::Schema.define do
|
|
115
116
|
t.boolean :active, default: false
|
116
117
|
t.text :data
|
117
118
|
t.text :json_data
|
119
|
+
t.text :unspecified_data
|
120
|
+
t.text :custom_data
|
118
121
|
end
|
119
122
|
|
120
123
|
create_table :promotions, primary_key: :promotion_id, force: :cascade do |t|
|
@@ -131,7 +134,8 @@ ActiveRecord::Schema.define do
|
|
131
134
|
t.string :discountable_type
|
132
135
|
end
|
133
136
|
|
134
|
-
create_table :rules, force: :cascade do |t|
|
137
|
+
create_table :rules, id: false, force: :cascade do |t|
|
138
|
+
t.integer :id
|
135
139
|
t.string :condition_text
|
136
140
|
t.integer :question_id
|
137
141
|
end
|
@@ -139,4 +143,12 @@ ActiveRecord::Schema.define do
|
|
139
143
|
create_table :questions, force: :cascade do |t|
|
140
144
|
t.string :body
|
141
145
|
end
|
146
|
+
|
147
|
+
create_table :vendors, force: :cascade do |t|
|
148
|
+
t.string :name, null: true
|
149
|
+
t.text :preferences
|
150
|
+
t.text :data
|
151
|
+
t.text :config
|
152
|
+
t.text :settings
|
153
|
+
end
|
142
154
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/postgresql_schema')
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/postgresql_schema')
|
@@ -0,0 +1,41 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
execute('CREATE extension IF NOT EXISTS "hstore";')
|
3
|
+
execute('CREATE extension IF NOT EXISTS "uuid-ossp";')
|
4
|
+
|
5
|
+
create_table :vendors, id: :uuid, force: :cascade do |t|
|
6
|
+
t.string :name, null: true
|
7
|
+
t.text :preferences
|
8
|
+
|
9
|
+
if t.respond_to?(:json)
|
10
|
+
t.json :data
|
11
|
+
else
|
12
|
+
t.text :data
|
13
|
+
end
|
14
|
+
|
15
|
+
if t.respond_to?(:hstore)
|
16
|
+
t.hstore :config
|
17
|
+
else
|
18
|
+
t.text :config
|
19
|
+
end
|
20
|
+
|
21
|
+
if t.respond_to?(:jsonb)
|
22
|
+
t.jsonb :settings
|
23
|
+
else
|
24
|
+
t.text :settings
|
25
|
+
end
|
26
|
+
|
27
|
+
t.datetime :created_at
|
28
|
+
t.datetime :updated_at
|
29
|
+
end
|
30
|
+
|
31
|
+
create_table :alarms, force: true do |t|
|
32
|
+
t.column :device_id, :integer, null: false
|
33
|
+
t.column :alarm_type, :integer, null: false
|
34
|
+
t.column :status, :integer, null: false
|
35
|
+
t.column :metadata, :text
|
36
|
+
t.datetime :created_at
|
37
|
+
t.datetime :updated_at
|
38
|
+
end
|
39
|
+
|
40
|
+
add_index :alarms, [:device_id, :alarm_type], unique: true, where: 'status <> 0'
|
41
|
+
end
|
data/test/sqlite3/import_test.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
2
|
|
3
3
|
should_support_recursive_import
|
4
|
+
should_support_on_duplicate_key_ignore
|
4
5
|
|
5
6
|
describe "#supports_imports?" do
|
6
7
|
context "and SQLite is 3.7.11 or higher" do
|
@@ -49,4 +50,18 @@ describe "#import" do
|
|
49
50
|
assert_equal 2500, Topic.count, "Failed to insert all records. Make sure you have a supported version of SQLite3 (3.7.11 or higher) installed"
|
50
51
|
end
|
51
52
|
end
|
53
|
+
|
54
|
+
context "with :on_duplicate_key_update" do
|
55
|
+
let(:topics) { Build(1, :topics) }
|
56
|
+
|
57
|
+
it "should log a warning message" do
|
58
|
+
log = StringIO.new
|
59
|
+
logger = Logger.new(log)
|
60
|
+
logger.level = Logger::WARN
|
61
|
+
ActiveRecord::Base.connection.stubs(:logger).returns(logger)
|
62
|
+
|
63
|
+
Topic.import topics, on_duplicate_key_update: true
|
64
|
+
assert_match(/Ignoring on_duplicate_key_update/, log.string)
|
65
|
+
end
|
66
|
+
end
|
52
67
|
end
|
data/test/support/factories.rb
CHANGED
@@ -4,6 +4,7 @@ def should_support_mysql_import_functionality
|
|
4
4
|
ActiveRecord::Base.connection.execute "set sql_mode='STRICT_ALL_TABLES'"
|
5
5
|
|
6
6
|
should_support_basic_on_duplicate_key_update
|
7
|
+
should_support_on_duplicate_key_ignore
|
7
8
|
|
8
9
|
describe "#import" do
|
9
10
|
context "with :on_duplicate_key_update and validation checks turned off" do
|
@@ -58,10 +58,57 @@ def should_support_postgresql_import_functionality
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
end
|
61
|
+
|
62
|
+
describe "with a uuid primary key" do
|
63
|
+
let(:vendor) { Vendor.new(name: "foo") }
|
64
|
+
let(:vendors) { [vendor] }
|
65
|
+
|
66
|
+
it "creates records" do
|
67
|
+
assert_difference "Vendor.count", +1 do
|
68
|
+
Vendor.import vendors
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it "assigns an id to the model objects" do
|
73
|
+
Vendor.import vendors
|
74
|
+
assert_not_nil vendor.id
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "with store accessor fields" do
|
79
|
+
if ENV['AR_VERSION'].to_f >= 4.0
|
80
|
+
it "imports values for json fields" do
|
81
|
+
vendors = [Vendor.new(name: 'Vendor 1', size: 100)]
|
82
|
+
assert_difference "Vendor.count", +1 do
|
83
|
+
Vendor.import vendors
|
84
|
+
end
|
85
|
+
assert_equal(100, Vendor.first.size)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "imports values for hstore fields" do
|
89
|
+
vendors = [Vendor.new(name: 'Vendor 1', contact: 'John Smith')]
|
90
|
+
assert_difference "Vendor.count", +1 do
|
91
|
+
Vendor.import vendors
|
92
|
+
end
|
93
|
+
assert_equal('John Smith', Vendor.first.contact)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
if ENV['AR_VERSION'].to_f >= 4.2
|
98
|
+
it "imports values for jsonb fields" do
|
99
|
+
vendors = [Vendor.new(name: 'Vendor 1', charge_code: '12345')]
|
100
|
+
assert_difference "Vendor.count", +1 do
|
101
|
+
Vendor.import vendors
|
102
|
+
end
|
103
|
+
assert_equal('12345', Vendor.first.charge_code)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
61
107
|
end
|
62
108
|
|
63
109
|
def should_support_postgresql_upsert_functionality
|
64
110
|
should_support_basic_on_duplicate_key_update
|
111
|
+
should_support_on_duplicate_key_ignore
|
65
112
|
|
66
113
|
describe "#import" do
|
67
114
|
extend ActiveSupport::TestCase::ImportAssertions
|
@@ -151,6 +198,50 @@ def should_support_postgresql_upsert_functionality
|
|
151
198
|
end
|
152
199
|
end
|
153
200
|
|
201
|
+
context 'with :index_predicate' do
|
202
|
+
let(:columns) { %w( id device_id alarm_type status metadata ) }
|
203
|
+
let(:values) { [[99, 17, 1, 1, 'foo']] }
|
204
|
+
let(:updated_values) { [[99, 17, 1, 2, 'bar']] }
|
205
|
+
|
206
|
+
macro(:perform_import) do |*opts|
|
207
|
+
Alarm.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: { conflict_target: [:device_id, :alarm_type], index_predicate: 'status <> 0', columns: [:status] }, validate: false)
|
208
|
+
end
|
209
|
+
|
210
|
+
macro(:updated_alarm) { Alarm.find(@alarm.id) }
|
211
|
+
|
212
|
+
setup do
|
213
|
+
Alarm.import columns, values, validate: false
|
214
|
+
@alarm = Alarm.find 99
|
215
|
+
end
|
216
|
+
|
217
|
+
context 'supports on duplicate key update for partial indexes' do
|
218
|
+
it 'should not update created_at timestamp columns' do
|
219
|
+
Timecop.freeze Chronic.parse("5 minutes from now") do
|
220
|
+
perform_import
|
221
|
+
assert_in_delta @alarm.created_at.to_i, updated_alarm.created_at.to_i, 1
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'should update updated_at timestamp columns' do
|
226
|
+
time = Chronic.parse("5 minutes from now")
|
227
|
+
Timecop.freeze time do
|
228
|
+
perform_import
|
229
|
+
assert_in_delta time.to_i, updated_alarm.updated_at.to_i, 1
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'should not update fields not mentioned' do
|
234
|
+
perform_import
|
235
|
+
assert_equal 'foo', updated_alarm.metadata
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'should update fields mentioned with hash mappings' do
|
239
|
+
perform_import
|
240
|
+
assert_equal 2, updated_alarm.status
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
154
245
|
context "with :constraint_name" do
|
155
246
|
let(:columns) { %w( id title author_name author_email_address parent_id ) }
|
156
247
|
let(:values) { [[100, "Book", "John Doe", "john@doe.com", 17]] }
|
@@ -0,0 +1,25 @@
|
|
1
|
+
def should_support_on_duplicate_key_ignore
|
2
|
+
describe "#import" do
|
3
|
+
extend ActiveSupport::TestCase::ImportAssertions
|
4
|
+
let(:topic) { Topic.create!(title: "Book", author_name: "John Doe") }
|
5
|
+
let(:topics) { [topic] }
|
6
|
+
|
7
|
+
context "with :on_duplicate_key_ignore" do
|
8
|
+
it "should skip duplicates and continue import" do
|
9
|
+
topics << Topic.new(title: "Book 2", author_name: "Jane Doe")
|
10
|
+
assert_difference "Topic.count", +1 do
|
11
|
+
Topic.import topics, on_duplicate_key_ignore: true, validate: false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "with :ignore" do
|
17
|
+
it "should skip duplicates and continue import" do
|
18
|
+
topics << Topic.new(title: "Book 2", author_name: "Jane Doe")
|
19
|
+
assert_difference "Topic.count", +1 do
|
20
|
+
Topic.import topics, ignore: true, validate: false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -48,6 +48,22 @@ def should_support_recursive_import
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
it 'imports polymorphic associations from subclass' do
|
52
|
+
discounts = Array.new(1) { |i| Discount.new(amount: i) }
|
53
|
+
dictionaries = Array.new(1) { |i| Dictionary.new(author_name: "Author ##{i}", title: "Book ##{i}") }
|
54
|
+
dictionaries.each do |dictionary|
|
55
|
+
dictionary.discounts << discounts
|
56
|
+
end
|
57
|
+
Dictionary.import dictionaries, recursive: true
|
58
|
+
assert_equal 1, Dictionary.last.discounts.count
|
59
|
+
dictionaries.each do |dictionary|
|
60
|
+
dictionary.discounts.each do |discount|
|
61
|
+
assert_not_nil discount.discountable_id
|
62
|
+
assert_equal 'Book', discount.discountable_type
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
51
67
|
[{ recursive: false }, {}].each do |import_options|
|
52
68
|
it "skips recursion for #{import_options}" do
|
53
69
|
assert_difference "Book.count", 0 do
|
@@ -106,14 +122,16 @@ def should_support_recursive_import
|
|
106
122
|
|
107
123
|
# If adapter supports on_duplicate_key_update, it is only applied to top level models so that SQL with invalid
|
108
124
|
# columns, keys, etc isn't generated for child associations when doing recursive import
|
109
|
-
|
110
|
-
|
125
|
+
if ActiveRecord::Base.connection.supports_on_duplicate_key_update?
|
126
|
+
describe "on_duplicate_key_update" do
|
127
|
+
let(:new_topics) { Build(1, :topic_with_book) }
|
111
128
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
129
|
+
it "imports objects with associations" do
|
130
|
+
assert_difference "Topic.count", +1 do
|
131
|
+
Topic.import new_topics, recursive: true, on_duplicate_key_update: [:updated_at], validate: false
|
132
|
+
new_topics.each do |topic|
|
133
|
+
assert_not_nil topic.id
|
134
|
+
end
|
117
135
|
end
|
118
136
|
end
|
119
137
|
end
|
data/test/test_helper.rb
CHANGED
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.16.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: 2016-
|
11
|
+
date: 2016-10-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -111,8 +111,10 @@ files:
|
|
111
111
|
- test/import_test.rb
|
112
112
|
- test/jdbcmysql/import_test.rb
|
113
113
|
- test/jdbcpostgresql/import_test.rb
|
114
|
+
- test/models/alarm.rb
|
114
115
|
- test/models/book.rb
|
115
116
|
- test/models/chapter.rb
|
117
|
+
- test/models/dictionary.rb
|
116
118
|
- test/models/discount.rb
|
117
119
|
- test/models/end_note.rb
|
118
120
|
- test/models/group.rb
|
@@ -120,6 +122,7 @@ files:
|
|
120
122
|
- test/models/question.rb
|
121
123
|
- test/models/rule.rb
|
122
124
|
- test/models/topic.rb
|
125
|
+
- test/models/vendor.rb
|
123
126
|
- test/models/widget.rb
|
124
127
|
- test/mysql2/import_test.rb
|
125
128
|
- test/mysql2_makara/import_test.rb
|
@@ -127,7 +130,10 @@ files:
|
|
127
130
|
- test/postgis/import_test.rb
|
128
131
|
- test/postgresql/import_test.rb
|
129
132
|
- test/schema/generic_schema.rb
|
133
|
+
- test/schema/jdbcpostgresql_schema.rb
|
130
134
|
- test/schema/mysql_schema.rb
|
135
|
+
- test/schema/postgis_schema.rb
|
136
|
+
- test/schema/postgresql_schema.rb
|
131
137
|
- test/schema/version.rb
|
132
138
|
- test/sqlite3/import_test.rb
|
133
139
|
- test/support/active_support/test_case_extensions.rb
|
@@ -136,6 +142,7 @@ files:
|
|
136
142
|
- test/support/generate.rb
|
137
143
|
- test/support/mysql/import_examples.rb
|
138
144
|
- test/support/postgresql/import_examples.rb
|
145
|
+
- test/support/shared_examples/on_duplicate_key_ignore.rb
|
139
146
|
- test/support/shared_examples/on_duplicate_key_update.rb
|
140
147
|
- test/support/shared_examples/recursive_import.rb
|
141
148
|
- test/synchronize_test.rb
|
@@ -163,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
163
170
|
version: '0'
|
164
171
|
requirements: []
|
165
172
|
rubyforge_project:
|
166
|
-
rubygems_version: 2.
|
173
|
+
rubygems_version: 2.4.8
|
167
174
|
signing_key:
|
168
175
|
specification_version: 4
|
169
176
|
summary: Bulk-loading extension for ActiveRecord
|
@@ -183,8 +190,10 @@ test_files:
|
|
183
190
|
- test/import_test.rb
|
184
191
|
- test/jdbcmysql/import_test.rb
|
185
192
|
- test/jdbcpostgresql/import_test.rb
|
193
|
+
- test/models/alarm.rb
|
186
194
|
- test/models/book.rb
|
187
195
|
- test/models/chapter.rb
|
196
|
+
- test/models/dictionary.rb
|
188
197
|
- test/models/discount.rb
|
189
198
|
- test/models/end_note.rb
|
190
199
|
- test/models/group.rb
|
@@ -192,6 +201,7 @@ test_files:
|
|
192
201
|
- test/models/question.rb
|
193
202
|
- test/models/rule.rb
|
194
203
|
- test/models/topic.rb
|
204
|
+
- test/models/vendor.rb
|
195
205
|
- test/models/widget.rb
|
196
206
|
- test/mysql2/import_test.rb
|
197
207
|
- test/mysql2_makara/import_test.rb
|
@@ -199,7 +209,10 @@ test_files:
|
|
199
209
|
- test/postgis/import_test.rb
|
200
210
|
- test/postgresql/import_test.rb
|
201
211
|
- test/schema/generic_schema.rb
|
212
|
+
- test/schema/jdbcpostgresql_schema.rb
|
202
213
|
- test/schema/mysql_schema.rb
|
214
|
+
- test/schema/postgis_schema.rb
|
215
|
+
- test/schema/postgresql_schema.rb
|
203
216
|
- test/schema/version.rb
|
204
217
|
- test/sqlite3/import_test.rb
|
205
218
|
- test/support/active_support/test_case_extensions.rb
|
@@ -208,6 +221,7 @@ test_files:
|
|
208
221
|
- test/support/generate.rb
|
209
222
|
- test/support/mysql/import_examples.rb
|
210
223
|
- test/support/postgresql/import_examples.rb
|
224
|
+
- test/support/shared_examples/on_duplicate_key_ignore.rb
|
211
225
|
- test/support/shared_examples/on_duplicate_key_update.rb
|
212
226
|
- test/support/shared_examples/recursive_import.rb
|
213
227
|
- test/synchronize_test.rb
|
@@ -215,4 +229,3 @@ test_files:
|
|
215
229
|
- test/travis/database.yml
|
216
230
|
- test/value_sets_bytes_parser_test.rb
|
217
231
|
- test/value_sets_records_parser_test.rb
|
218
|
-
has_rdoc:
|