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