activerecord-import 0.15.0 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|