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