activerecord-import 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.markdown +19 -0
- data/gemfiles/3.1.gemfile +1 -1
- data/gemfiles/3.2.gemfile +1 -1
- data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +0 -1
- data/lib/activerecord-import/adapters/abstract_adapter.rb +1 -1
- data/lib/activerecord-import/adapters/mysql_adapter.rb +2 -2
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +27 -0
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +1 -1
- data/lib/activerecord-import/import.rb +95 -20
- data/lib/activerecord-import/version.rb +1 -1
- data/test/models/book.rb +3 -1
- data/test/models/chapter.rb +4 -0
- data/test/models/end_note.rb +4 -0
- data/test/models/topic.rb +1 -1
- data/test/postgresql/import_test.rb +1 -1
- data/test/schema/generic_schema.rb +15 -0
- data/test/support/factories.rb +20 -0
- data/test/support/postgresql/import_examples.rb +84 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aad783ef61c17fc4254414ec61467e9f3a2feb87
|
4
|
+
data.tar.gz: 6f6724d8165495a08b09621024ddaa7178af3049
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f757f8b390437bace4de12912a69e2638ee6bf3dd9a0d1092134337325052a1e00c31e8e3eb59e3fd7c1064d0fae8e8c935f521dfcdebff540334f8290095dec
|
7
|
+
data.tar.gz: aebcf07390a107d36309e34dcf121d2cae7f0264e7b39d403afc7fa3fad19fe9c1d0575e36ff0590c95546bdaf0e2480b5efe4fb303b2a02b134c3a0dff06fef
|
data/Gemfile
CHANGED
data/README.markdown
CHANGED
@@ -2,6 +2,25 @@
|
|
2
2
|
|
3
3
|
activerecord-import is a library for bulk inserting data using ActiveRecord.
|
4
4
|
|
5
|
+
One of its major features is following activerecord associations and generating the minimal
|
6
|
+
number of SQL insert statements required, avoiding the N+1 insert problem. An example probably
|
7
|
+
explains it best. Say you had a schema like this:
|
8
|
+
|
9
|
+
Publishers have Books
|
10
|
+
Books have Reviews
|
11
|
+
|
12
|
+
and you wanted to bulk insert 100 new publishers with 10K books and 3 reviews per book. This library will follow the associations
|
13
|
+
down and generate only 3 SQL insert statements - one for the publishers, one for the books, and one for the reviews.
|
14
|
+
|
15
|
+
In contrast, the standard ActiveRecord save would generate
|
16
|
+
100 insert statements for the publishers, then it would visit each publisher and save all the books:
|
17
|
+
100 * 10,000 = 1,000,000 SQL insert statements
|
18
|
+
and then the reviews:
|
19
|
+
100 * 10,000 * 3 = 3M SQL insert statements,
|
20
|
+
|
21
|
+
That would be about 4M SQL insert statements vs 3, which results in vastly improved performance. In our case, it converted
|
22
|
+
an 18 hour batch process to <2 hrs.
|
23
|
+
|
5
24
|
### Rails 4.0
|
6
25
|
|
7
26
|
Use activerecord-import 0.4.0 or higher.
|
data/gemfiles/3.1.gemfile
CHANGED
data/gemfiles/3.2.gemfile
CHANGED
@@ -5,7 +5,7 @@ module ActiveRecord::Import::MysqlAdapter
|
|
5
5
|
NO_MAX_PACKET = 0
|
6
6
|
QUERY_OVERHEAD = 8 #This was shown to be true for MySQL, but it's not clear where the overhead is from.
|
7
7
|
|
8
|
-
# +sql+ can be a single string or an array. If it is an array all
|
8
|
+
# +sql+ can be a single string or an array. If it is an array all
|
9
9
|
# elements that are in position >= 1 will be appended to the final SQL.
|
10
10
|
def insert_many( sql, values, *args ) # :nodoc:
|
11
11
|
# the number of inserts default
|
@@ -46,7 +46,7 @@ module ActiveRecord::Import::MysqlAdapter
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
number_of_inserts
|
49
|
+
[number_of_inserts,[]]
|
50
50
|
end
|
51
51
|
|
52
52
|
# Returns the maximum number of bytes that the server will allow
|
@@ -1,7 +1,34 @@
|
|
1
1
|
module ActiveRecord::Import::PostgreSQLAdapter
|
2
2
|
include ActiveRecord::Import::ImportSupport
|
3
3
|
|
4
|
+
def insert_many( sql, values, *args ) # :nodoc:
|
5
|
+
number_of_inserts = 1
|
6
|
+
|
7
|
+
base_sql,post_sql = if sql.is_a?( String )
|
8
|
+
[ sql, '' ]
|
9
|
+
elsif sql.is_a?( Array )
|
10
|
+
[ sql.shift, sql.join( ' ' ) ]
|
11
|
+
end
|
12
|
+
|
13
|
+
sql2insert = base_sql + values.join( ',' ) + post_sql
|
14
|
+
ids = select_values( sql2insert, *args )
|
15
|
+
|
16
|
+
[number_of_inserts,ids]
|
17
|
+
end
|
18
|
+
|
4
19
|
def next_value_for_sequence(sequence_name)
|
5
20
|
%{nextval('#{sequence_name}')}
|
6
21
|
end
|
22
|
+
|
23
|
+
def post_sql_statements( table_name, options ) # :nodoc:
|
24
|
+
unless options[:primary_key].blank?
|
25
|
+
super(table_name, options) << (" RETURNING #{options[:primary_key]}")
|
26
|
+
else
|
27
|
+
super(table_name, options)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def support_setting_primary_key_of_imported_objects?
|
32
|
+
true
|
33
|
+
end
|
7
34
|
end
|
@@ -3,7 +3,7 @@ require "ostruct"
|
|
3
3
|
module ActiveRecord::Import::ConnectionAdapters ; end
|
4
4
|
|
5
5
|
module ActiveRecord::Import #:nodoc:
|
6
|
-
class Result < Struct.new(:failed_instances, :num_inserts)
|
6
|
+
class Result < Struct.new(:failed_instances, :num_inserts, :ids)
|
7
7
|
end
|
8
8
|
|
9
9
|
module ImportSupport #:nodoc:
|
@@ -68,7 +68,7 @@ class ActiveRecord::Associations::CollectionAssociation
|
|
68
68
|
|
69
69
|
# supports empty array
|
70
70
|
elsif args.last.is_a?( Array ) and args.last.empty?
|
71
|
-
return ActiveRecord::Import::Result.new([], 0) if args.last.empty?
|
71
|
+
return ActiveRecord::Import::Result.new([], 0, []) if args.last.empty?
|
72
72
|
|
73
73
|
# supports 2-element array and array
|
74
74
|
elsif args.size == 2 and args.first.is_a?( Array ) and args.last.is_a?( Array )
|
@@ -109,18 +109,21 @@ class ActiveRecord::Base
|
|
109
109
|
# Returns true if the current database connection adapter
|
110
110
|
# supports import functionality, otherwise returns false.
|
111
111
|
def supports_import?(*args)
|
112
|
-
connection.supports_import?(*args)
|
113
|
-
rescue NoMethodError
|
114
|
-
false
|
112
|
+
connection.respond_to?(:supports_import?) && connection.supports_import?(*args)
|
115
113
|
end
|
116
114
|
|
117
115
|
# Returns true if the current database connection adapter
|
118
116
|
# supports on duplicate key update functionality, otherwise
|
119
117
|
# returns false.
|
120
118
|
def supports_on_duplicate_key_update?
|
121
|
-
connection.supports_on_duplicate_key_update?
|
122
|
-
|
123
|
-
|
119
|
+
connection.respond_to?(:supports_on_duplicate_key_update?) && connection.supports_on_duplicate_key_update?
|
120
|
+
end
|
121
|
+
|
122
|
+
# returns true if the current database connection adapter
|
123
|
+
# supports setting the primary key of bulk imported models, otherwise
|
124
|
+
# returns false
|
125
|
+
def support_setting_primary_key_of_imported_objects?
|
126
|
+
connection.respond_to?(:support_setting_primary_key_of_imported_objects?) && connection.support_setting_primary_key_of_imported_objects?
|
124
127
|
end
|
125
128
|
|
126
129
|
# Imports a collection of values to the database.
|
@@ -172,6 +175,9 @@ class ActiveRecord::Base
|
|
172
175
|
# existing model instances in memory with updates from the import.
|
173
176
|
# * +timestamps+ - true|false, tells import to not add timestamps \
|
174
177
|
# (if false) even if record timestamps is disabled in ActiveRecord::Base
|
178
|
+
# * +recursive - true|false, tells import to import all autosave association
|
179
|
+
# if the adapter supports setting the primary keys of the newly imported
|
180
|
+
# objects.
|
175
181
|
#
|
176
182
|
# == Examples
|
177
183
|
# class BlogPost < ActiveRecord::Base ; end
|
@@ -230,11 +236,24 @@ class ActiveRecord::Base
|
|
230
236
|
# This returns an object which responds to +failed_instances+ and +num_inserts+.
|
231
237
|
# * failed_instances - an array of objects that fails validation and were not committed to the database. An empty array if no validation is performed.
|
232
238
|
# * num_inserts - the number of insert statements it took to import the data
|
233
|
-
|
234
|
-
|
239
|
+
# * ids - the priamry keys of the imported ids, if the adpater supports it, otherwise and empty array.
|
240
|
+
def import(*args)
|
241
|
+
if args.first.is_a?( Array ) and args.first.first.is_a? ActiveRecord::Base
|
242
|
+
options = {}
|
243
|
+
options.merge!( args.pop ) if args.last.is_a?(Hash)
|
244
|
+
|
245
|
+
models = args.first
|
246
|
+
import_helper(models, options)
|
247
|
+
else
|
248
|
+
import_helper(*args)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def import_helper( *args )
|
253
|
+
options = { :validate=>true, :timestamps=>true, :primary_key=>primary_key }
|
235
254
|
options.merge!( args.pop ) if args.last.is_a? Hash
|
236
255
|
|
237
|
-
is_validating = options
|
256
|
+
is_validating = options[:validate]
|
238
257
|
is_validating = true unless options[:validate_with_context].nil?
|
239
258
|
|
240
259
|
# assume array of model objects
|
@@ -257,7 +276,7 @@ class ActiveRecord::Base
|
|
257
276
|
end
|
258
277
|
# supports empty array
|
259
278
|
elsif args.last.is_a?( Array ) and args.last.empty?
|
260
|
-
return ActiveRecord::Import::Result.new([], 0) if args.last.empty?
|
279
|
+
return ActiveRecord::Import::Result.new([], 0, []) if args.last.empty?
|
261
280
|
# supports 2-element array and array
|
262
281
|
elsif args.size == 2 and args.first.is_a?( Array ) and args.last.is_a?( Array )
|
263
282
|
column_names, array_of_attributes = args
|
@@ -284,16 +303,26 @@ class ActiveRecord::Base
|
|
284
303
|
return_obj = if is_validating
|
285
304
|
import_with_validations( column_names, array_of_attributes, options )
|
286
305
|
else
|
287
|
-
num_inserts = import_without_validations_or_callbacks( column_names, array_of_attributes, options )
|
288
|
-
ActiveRecord::Import::Result.new([], num_inserts)
|
306
|
+
(num_inserts, ids) = import_without_validations_or_callbacks( column_names, array_of_attributes, options )
|
307
|
+
ActiveRecord::Import::Result.new([], num_inserts, ids)
|
289
308
|
end
|
290
309
|
|
291
310
|
if options[:synchronize]
|
292
311
|
sync_keys = options[:synchronize_keys] || [self.primary_key]
|
293
312
|
synchronize( options[:synchronize], sync_keys)
|
294
313
|
end
|
295
|
-
|
296
314
|
return_obj.num_inserts = 0 if return_obj.num_inserts.nil?
|
315
|
+
|
316
|
+
# if we have ids, then set the id on the models and mark the models as clean.
|
317
|
+
if support_setting_primary_key_of_imported_objects?
|
318
|
+
set_ids_and_mark_clean(models, return_obj)
|
319
|
+
|
320
|
+
# if there are auto-save associations on the models we imported that are new, import them as well
|
321
|
+
if options[:recursive]
|
322
|
+
import_associations(models, options)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
297
326
|
return_obj
|
298
327
|
end
|
299
328
|
|
@@ -327,12 +356,12 @@ class ActiveRecord::Base
|
|
327
356
|
end
|
328
357
|
array_of_attributes.compact!
|
329
358
|
|
330
|
-
num_inserts = if array_of_attributes.empty? || options[:all_or_none] && failed_instances.any?
|
331
|
-
0
|
359
|
+
(num_inserts, ids) = if array_of_attributes.empty? || options[:all_or_none] && failed_instances.any?
|
360
|
+
[0,[]]
|
332
361
|
else
|
333
362
|
import_without_validations_or_callbacks( column_names, array_of_attributes, options )
|
334
363
|
end
|
335
|
-
ActiveRecord::Import::Result.new(failed_instances, num_inserts)
|
364
|
+
ActiveRecord::Import::Result.new(failed_instances, num_inserts, ids)
|
336
365
|
end
|
337
366
|
|
338
367
|
# Imports the passed in +column_names+ and +array_of_attributes+
|
@@ -364,6 +393,7 @@ class ActiveRecord::Base
|
|
364
393
|
columns_sql = "(#{column_names.map{|name| connection.quote_column_name(name) }.join(',')})"
|
365
394
|
insert_sql = "INSERT #{options[:ignore] ? 'IGNORE ':''}INTO #{quoted_table_name} #{columns_sql} VALUES "
|
366
395
|
values_sql = values_sql_for_columns_and_attributes(columns, array_of_attributes)
|
396
|
+
ids = []
|
367
397
|
if not supports_import?
|
368
398
|
number_inserted = 0
|
369
399
|
values_sql.each do |values|
|
@@ -375,15 +405,60 @@ class ActiveRecord::Base
|
|
375
405
|
post_sql_statements = connection.post_sql_statements( quoted_table_name, options )
|
376
406
|
|
377
407
|
# perform the inserts
|
378
|
-
number_inserted = connection.insert_many( [ insert_sql, post_sql_statements ].flatten,
|
408
|
+
(number_inserted,ids) = connection.insert_many( [ insert_sql, post_sql_statements ].flatten,
|
379
409
|
values_sql,
|
380
410
|
"#{self.class.name} Create Many Without Validations Or Callbacks" )
|
381
411
|
end
|
382
|
-
number_inserted
|
412
|
+
[number_inserted, ids]
|
383
413
|
end
|
384
414
|
|
385
415
|
private
|
386
416
|
|
417
|
+
def set_ids_and_mark_clean(models, import_result)
|
418
|
+
unless models.nil?
|
419
|
+
import_result.ids.each_with_index do |id, index|
|
420
|
+
models[index].id = id.to_i
|
421
|
+
models[index].instance_variable_get(:@changed_attributes).clear # mark the model as saved
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
def import_associations(models, options)
|
427
|
+
# now, for all the dirty associations, collect them into a new set of models, then recurse.
|
428
|
+
# notes:
|
429
|
+
# does not handle associations that reference themselves
|
430
|
+
# assumes that the only associations to be saved are marked with :autosave
|
431
|
+
# should probably take a hash to associations to follow.
|
432
|
+
associated_objects_by_class={}
|
433
|
+
models.each {|model| find_associated_objects_for_import(associated_objects_by_class, model) }
|
434
|
+
|
435
|
+
associated_objects_by_class.each_pair do |class_name, associations|
|
436
|
+
associations.each_pair do |association_name, associated_records|
|
437
|
+
associated_records.first.class.import(associated_records, options) unless associated_records.empty?
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
# We are eventually going to call Class.import <objects> so we build up a hash
|
443
|
+
# of class => objects to import.
|
444
|
+
def find_associated_objects_for_import(associated_objects_by_class, model)
|
445
|
+
associated_objects_by_class[model.class.name]||={}
|
446
|
+
|
447
|
+
model.class.reflect_on_all_autosave_associations.each do |association_reflection|
|
448
|
+
associated_objects_by_class[model.class.name][association_reflection.name]||=[]
|
449
|
+
|
450
|
+
association = model.association(association_reflection.name)
|
451
|
+
association.loaded!
|
452
|
+
|
453
|
+
changed_objects = association.select {|a| a.new_record? || a.changed?}
|
454
|
+
changed_objects.each do |child|
|
455
|
+
child.send("#{association_reflection.foreign_key}=", model.id)
|
456
|
+
end
|
457
|
+
associated_objects_by_class[model.class.name][association_reflection.name].concat changed_objects
|
458
|
+
end
|
459
|
+
associated_objects_by_class
|
460
|
+
end
|
461
|
+
|
387
462
|
# Returns SQL the VALUES for an INSERT statement given the passed in +columns+
|
388
463
|
# and +array_of_attributes+.
|
389
464
|
def values_sql_for_columns_and_attributes(columns, array_of_attributes) # :nodoc:
|
data/test/models/book.rb
CHANGED
data/test/models/topic.rb
CHANGED
@@ -2,7 +2,7 @@ class Topic < ActiveRecord::Base
|
|
2
2
|
validates_presence_of :author_name
|
3
3
|
validates :title, numericality: { only_integer: true }, on: :context_test
|
4
4
|
|
5
|
-
has_many :books
|
5
|
+
has_many :books, :autosave=>true, :inverse_of=>:topic
|
6
6
|
belongs_to :parent, :class_name => "Topic"
|
7
7
|
|
8
8
|
composed_of :description, :mapping => [ %w(title title), %w(author_name author_name)], :allow_nil => true, :class_name => "TopicDescription"
|
@@ -66,6 +66,21 @@ ActiveRecord::Schema.define do
|
|
66
66
|
t.column :for_sale, :boolean, :default => true
|
67
67
|
end
|
68
68
|
|
69
|
+
create_table :chapters, :force => true do |t|
|
70
|
+
t.column :title, :string
|
71
|
+
t.column :book_id, :integer, :null => false
|
72
|
+
t.column :created_at, :datetime
|
73
|
+
t.column :updated_at, :datetime
|
74
|
+
end
|
75
|
+
|
76
|
+
create_table :end_notes, :force => true do |t|
|
77
|
+
t.column :note, :string
|
78
|
+
t.column :book_id, :integer, :null => false
|
79
|
+
t.column :created_at, :datetime
|
80
|
+
t.column :updated_at, :datetime
|
81
|
+
end
|
82
|
+
|
83
|
+
|
69
84
|
create_table :languages, :force=>true do |t|
|
70
85
|
t.column :name, :string
|
71
86
|
t.column :developer_id, :integer
|
data/test/support/factories.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
FactoryGirl.define do
|
2
|
+
sequence(:book_title) {|n| "Book #{n}"}
|
3
|
+
sequence(:chapter_title) {|n| "Chapter #{n}"}
|
4
|
+
sequence(:end_note) {|n| "Endnote #{n}"}
|
5
|
+
|
2
6
|
factory :group do
|
3
7
|
sequence(:order) { |n| "Order #{n}" }
|
4
8
|
end
|
@@ -16,4 +20,20 @@ FactoryGirl.define do
|
|
16
20
|
factory :widget do
|
17
21
|
sequence(:w_id){ |n| n}
|
18
22
|
end
|
23
|
+
|
24
|
+
factory :topic_with_book, :parent=>:topic do |m|
|
25
|
+
after(:build) do |topic|
|
26
|
+
2.times do
|
27
|
+
book = topic.books.build(:title=>FactoryGirl.generate(:book_title), :author_name=>'Stephen King')
|
28
|
+
3.times do
|
29
|
+
book.chapters.build(:title => FactoryGirl.generate(:chapter_title))
|
30
|
+
end
|
31
|
+
|
32
|
+
4.times do
|
33
|
+
book.end_notes.build(:note => FactoryGirl.generate(:end_note))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
19
39
|
end
|
@@ -17,5 +17,89 @@ def should_support_postgresql_import_functionality
|
|
17
17
|
assert_equal 1, result.num_inserts
|
18
18
|
end
|
19
19
|
end
|
20
|
+
|
21
|
+
describe "importing objects with associations" do
|
22
|
+
|
23
|
+
let(:new_topics) { Build(num_topics, :topic_with_book) }
|
24
|
+
let(:new_topics_with_invalid_chapter) {
|
25
|
+
chapter = new_topics.first.books.first.chapters.first
|
26
|
+
chapter.title = nil
|
27
|
+
new_topics
|
28
|
+
}
|
29
|
+
let(:num_topics) {3}
|
30
|
+
let(:num_books) {6}
|
31
|
+
let(:num_chapters) {18}
|
32
|
+
let(:num_endnotes) {24}
|
33
|
+
|
34
|
+
it 'imports top level' do
|
35
|
+
assert_difference "Topic.count", +num_topics do
|
36
|
+
Topic.import new_topics, :recursive => true
|
37
|
+
new_topics.each do |topic|
|
38
|
+
assert_not_nil topic.id
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'imports first level associations' do
|
44
|
+
assert_difference "Book.count", +num_books do
|
45
|
+
Topic.import new_topics, :recursive => true
|
46
|
+
new_topics.each do |topic|
|
47
|
+
topic.books.each do |book|
|
48
|
+
assert_equal topic.id, book.topic_id
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
[{:recursive => false}, {}].each do |import_options|
|
55
|
+
it "skips recursion for #{import_options.to_s}" do
|
56
|
+
assert_difference "Book.count", 0 do
|
57
|
+
Topic.import new_topics, import_options
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'imports deeper nested associations' do
|
63
|
+
assert_difference "Chapter.count", +num_chapters do
|
64
|
+
assert_difference "EndNote.count", +num_endnotes do
|
65
|
+
Topic.import new_topics, :recursive => true
|
66
|
+
new_topics.each do |topic|
|
67
|
+
topic.books.each do |book|
|
68
|
+
book.chapters.each do |chapter|
|
69
|
+
assert_equal book.id, chapter.book_id
|
70
|
+
end
|
71
|
+
book.end_notes.each do |endnote|
|
72
|
+
assert_equal book.id, endnote.book_id
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it "skips validation of the associations if requested" do
|
81
|
+
assert_difference "Chapter.count", +num_chapters do
|
82
|
+
Topic.import new_topics_with_invalid_chapter, :validate => false, :recursive => true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# These models dont validate associated. So we expect that books and topics get inserted, but not chapters
|
87
|
+
# Putting a transaction around everything wouldn't work, so if you want your chapters to prevent topics from
|
88
|
+
# being created, you would need to have validates_associated in your models and insert with validation
|
89
|
+
describe "all_or_none" do
|
90
|
+
[Book, Topic, EndNote].each do |type|
|
91
|
+
it "creates #{type.to_s}" do
|
92
|
+
assert_difference "#{type.to_s}.count", send("num_#{type.to_s.downcase}s") do
|
93
|
+
Topic.import new_topics_with_invalid_chapter, :all_or_none => true, :recursive => true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
it "doesn't create chapters" do
|
98
|
+
assert_difference "Chapter.count", 0 do
|
99
|
+
Topic.import new_topics_with_invalid_chapter, :all_or_none => true, :recursive => true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
20
104
|
end
|
21
105
|
end
|
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.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zach Dennis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-05-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -116,6 +116,8 @@ files:
|
|
116
116
|
- test/jdbcmysql/import_test.rb
|
117
117
|
- test/jdbcpostgresql/import_test.rb
|
118
118
|
- test/models/book.rb
|
119
|
+
- test/models/chapter.rb
|
120
|
+
- test/models/end_note.rb
|
119
121
|
- test/models/group.rb
|
120
122
|
- test/models/topic.rb
|
121
123
|
- test/models/widget.rb
|
@@ -184,6 +186,8 @@ test_files:
|
|
184
186
|
- test/jdbcmysql/import_test.rb
|
185
187
|
- test/jdbcpostgresql/import_test.rb
|
186
188
|
- test/models/book.rb
|
189
|
+
- test/models/chapter.rb
|
190
|
+
- test/models/end_note.rb
|
187
191
|
- test/models/group.rb
|
188
192
|
- test/models/topic.rb
|
189
193
|
- test/models/widget.rb
|
@@ -209,3 +213,4 @@ test_files:
|
|
209
213
|
- test/travis/database.yml
|
210
214
|
- test/value_sets_bytes_parser_test.rb
|
211
215
|
- test/value_sets_records_parser_test.rb
|
216
|
+
has_rdoc:
|