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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7c1b3ac9675567f96311618bfcf008c344559183
4
- data.tar.gz: 796d923d2db2f0317c832081e2ea336b9f933a7c
3
+ metadata.gz: 64717eaa9cb0c49761ad29e6aa358c87e96b7166
4
+ data.tar.gz: 879ac9c11ee4a9a62988c97608ad195a1052893d
5
5
  SHA512:
6
- metadata.gz: 17a0387cd93c752b91f8cee9bf0fa311463e927debf83141a759e277d5e632aa7f468fa0b36a082d54adad0596cae40f31002f699b6189c0b00ce1b072e666c9
7
- data.tar.gz: 587d1a167feb65558bf10208404a23bc409c6a4f48e8dd64cc32e0e2af76e1f9b81247a17658c47de102d29a0b8b4619a021d1b78bde61aa00fa5403b38c2aaa
6
+ metadata.gz: 7e06cfe24bc207291c4a4c8737f8c3d6309218ccde2ca3246089f45d7f36c85f6fa7a63abe5758934dd6c9e939422c40395880b7ba950e10db7b96fa38b071af
7
+ data.tar.gz: 6ce0fc279753ab340269eefa2d64be18c40d2bed279f3cda27ba4057cb428582a207c5637da5a7c5222cffa77d2a43a0030578c87ebe05f76c81ad098da3fa21
@@ -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 -U postgres -c "create extension postgis"
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
@@ -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
@@ -25,6 +25,7 @@ end
25
25
  gem "factory_girl", "~> 4.2.0"
26
26
  gem "timecop"
27
27
  gem "chronic"
28
+ gem "mocha"
28
29
 
29
30
  # Debugging
30
31
  platforms :jruby do
@@ -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=mysql --to-html=results.html
18
+ ruby benchmark.rb --adapter=mysql2 --to-html=results.html
19
19
 
20
20
  To output to csv format:
21
- ruby benchmark.rb --adapter=mysql --to-csv=results.csv
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
- if options[:on_duplicate_key_ignore] && respond_to?(:sql_for_on_duplicate_key_ignore)
50
- # Options :recursive and :on_duplicate_key_ignore are mutually exclusive
51
- unless options[:recursive]
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
- value_sets.each do |value_set|
43
- number_of_inserts += 1
44
- sql2insert = base_sql + value_set.join( ',' ) + post_sql
45
- insert( sql2insert, *args )
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
- else
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
- if options[:no_returning] || options[:primary_key].blank?
35
- super(table_name, options)
36
- else
37
- super(table_name, options) << "RETURNING #{options[:primary_key]}"
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
- value_sets.each do |value_set|
34
- number_of_inserts += 1
35
- sql2insert = base_sql + value_set.join( ',' ) + post_sql
36
- first_insert_id = insert( sql2insert, *args )
37
- last_insert_id = first_insert_id + value_set.size - 1
38
- ids.concat((first_insert_id..last_insert_id).to_a)
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, tells import to use MySQL's INSERT IGNORE
175
- # to discard records that contain duplicate keys.
176
- # * +on_duplicate_key_ignore+ - true|false, tells import to use
177
- # Postgres 9.5+ ON CONFLICT DO NOTHING. Cannot be enabled on a
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
- # two attributes, :conflict_target or :constraint_name and :columns.
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 :on_duplicate_update option can be a hash with up to two attributes,
264
- # :conflict_target or constraint_name, and :columns. Unlike MySQL, Postgres
265
- # requires the conflicting constraint to be explicitly specified. Using this
266
- # option allows you to specify a constraint other than the primary key.
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, if the adpater supports it, otherwise and empty array.
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, primary_key: primary_key }
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
- model.read_attribute_before_type_cast(name.to_s)
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] && model.respond_to?(:raise_validation_error, true) # Rails 5.0 and higher
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
- insert_sql = "INSERT #{options[:ignore] ? 'IGNORE ' : ''}INTO #{quoted_table_name} #{columns_sql} VALUES "
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
- values_sql.each do |values|
525
- ids << connection.insert(insert_sql + values)
526
- number_inserted += 1
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.to_i
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
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Import
3
- VERSION = "0.15.0".freeze
3
+ VERSION = "0.16.0".freeze
4
4
  end
5
5
  end
@@ -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
- if ENV['AR_VERSION'].to_f >= 3.1
496
- let(:data) { { a: :b } }
497
- it "imports values for serialized JSON fields" do
498
- assert_difference "Widget.unscoped.count", +1 do
499
- Widget.import [:w_id, :json_data], [[9, data]]
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(data.as_json, Widget.find_by_w_id(9).json_data)
572
+ assert_equal('blue', Vendor.first.color)
502
573
  end
503
574
  end
504
575
  end
505
576
 
506
577
  describe "#import!" do
507
- let(:columns) { %w(title author_name) }
508
- let(:valid_values) { [["LDAP", "Jerry Carter"], ["Rails Recipes", "Chad Fowler"]] }
509
- let(:invalid_values) { [["Rails Recipes", "Chad Fowler"], ["The RSpec Book", ""], ["Agile+UX", ""]] }
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
- context "with invalid data" do
512
- it "should raise ActiveRecord::RecordInvalid" do
513
- assert_no_difference "Topic.count" do
514
- assert_raise ActiveRecord::RecordInvalid do
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 valid data" do
522
- it "should import data" do
523
- assert_difference "Topic.count", +2 do
524
- Topic.import! columns, valid_values
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
@@ -0,0 +1,2 @@
1
+ class Alarm < ActiveRecord::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ class Dictionary < Book
2
+ end
@@ -0,0 +1,7 @@
1
+ class Vendor < ActiveRecord::Base
2
+ store :preferences, accessors: [:color], coder: JSON
3
+
4
+ store_accessor :data, :size
5
+ store_accessor :config, :contact
6
+ store_accessor :settings, :charge_code
7
+ end
@@ -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
@@ -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
@@ -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
@@ -32,6 +32,7 @@ FactoryGirl.define do
32
32
  end
33
33
 
34
34
  factory :rule do
35
+ sequence(:id) { |n| n }
35
36
  sequence(:condition_text) { |n| "q_#{n}_#{n}" }
36
37
  end
37
38
 
@@ -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
- describe "on_duplicate_key_update" do
110
- let(:new_topics) { Build(1, :topic_with_book) }
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
- it "imports objects with associations" do
113
- assert_difference "Topic.count", +1 do
114
- Topic.import new_topics, recursive: true, on_duplicate_key_update: [:updated_at], validate: false
115
- new_topics.each do |topic|
116
- assert_not_nil topic.id
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
@@ -18,8 +18,10 @@ require "active_support/test_case"
18
18
 
19
19
  if ActiveSupport::VERSION::STRING < "4.0"
20
20
  require 'test/unit'
21
+ require 'mocha/test_unit'
21
22
  else
22
23
  require 'active_support/testing/autorun'
24
+ require "mocha/mini_test"
23
25
  end
24
26
 
25
27
  require 'timecop'
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.15.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-07-04 00:00:00.000000000 Z
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.5.1
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: