activerecord-import 0.10.0 → 0.11.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/.travis.yml +1 -1
- data/Gemfile +6 -3
- data/README.markdown +3 -3
- data/benchmarks/benchmark.rb +24 -21
- data/benchmarks/lib/cli_parser.rb +22 -18
- data/benchmarks/lib/output_to_csv.rb +3 -3
- data/benchmarks/models/test_innodb.rb +1 -1
- data/benchmarks/models/test_memory.rb +1 -1
- data/benchmarks/models/test_myisam.rb +1 -1
- data/gemfiles/3.1.gemfile +1 -0
- data/gemfiles/3.2.gemfile +1 -0
- data/gemfiles/4.0.gemfile +2 -1
- data/gemfiles/4.1.gemfile +1 -0
- data/gemfiles/4.2.gemfile +1 -0
- data/gemfiles/5.0.gemfile +5 -0
- data/lib/activerecord-import.rb +13 -14
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +2 -0
- data/lib/activerecord-import/import.rb +29 -9
- data/lib/activerecord-import/synchronize.rb +2 -2
- data/lib/activerecord-import/version.rb +1 -1
- data/test/import_test.rb +46 -5
- data/test/models/widget.rb +2 -1
- data/test/schema/generic_schema.rb +1 -0
- data/test/support/active_support/test_case_extensions.rb +6 -0
- data/test/support/em-synchrony_extensions.rb +13 -0
- data/test/support/mysql/assertions.rb +9 -0
- data/test/support/mysql/import_examples.rb +32 -3
- data/test/support/postgresql/import_examples.rb +24 -0
- data/test/test_helper.rb +3 -1
- data/test/travis/build.sh +2 -2
- metadata +6 -5
- data/benchmarks/boot.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a877605fbbb5a9a7100063ad764a46d8a497708c
|
4
|
+
data.tar.gz: 4eee2b275df655fb6f63f47de0b1b60ae669629c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3457053406910b5e2d4d740ddb5dc2efa716f7ef8e54596998aec575568e62ebfa55570c46270c837bdc1f3c1e145e1ff954441ce58f881343f44cb9ea01be3
|
7
|
+
data.tar.gz: ae534e1dd917a93dc3c0907bba45e7d1e6a6fb553e1a3a86c08ab8d85cb019c322d70a7cc6c90e7c6b25367b059a730e2c6d886336abc7118d89e8fc22b253d4
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -2,12 +2,13 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
gemspec
|
4
4
|
|
5
|
+
gem "pry-byebug"
|
6
|
+
|
5
7
|
# Database Adapters
|
6
8
|
platforms :ruby do
|
7
|
-
gem "em-synchrony", "1.0.3"
|
8
9
|
gem "mysql2", "~> 0.3.0"
|
9
10
|
gem "pg", "~> 0.9"
|
10
|
-
gem "sqlite3
|
11
|
+
gem "sqlite3", "~> 1.3.10"
|
11
12
|
gem "seamless_database_pool", "~> 1.0.13"
|
12
13
|
end
|
13
14
|
|
@@ -37,10 +38,12 @@ platforms :mri_19 do
|
|
37
38
|
gem "debugger"
|
38
39
|
end
|
39
40
|
|
40
|
-
version = ENV['AR_VERSION'] || "
|
41
|
+
version = ENV['AR_VERSION'] || "4.2"
|
41
42
|
|
42
43
|
if version > "4.0"
|
43
44
|
gem "minitest"
|
45
|
+
else
|
46
|
+
gem "test-unit"
|
44
47
|
end
|
45
48
|
|
46
49
|
eval_gemfile File.expand_path("../gemfiles/#{version}.gemfile", __FILE__)
|
data/README.markdown
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# activerecord-import
|
1
|
+
# activerecord-import [](https://travis-ci.org/zdennis/activerecord-import)
|
2
2
|
|
3
3
|
activerecord-import is a library for bulk inserting data using ActiveRecord.
|
4
4
|
|
@@ -6,8 +6,8 @@ One of its major features is following activerecord associations and generating
|
|
6
6
|
number of SQL insert statements required, avoiding the N+1 insert problem. An example probably
|
7
7
|
explains it best. Say you had a schema like this:
|
8
8
|
|
9
|
-
Publishers have Books
|
10
|
-
Books have Reviews
|
9
|
+
- Publishers have Books
|
10
|
+
- Books have Reviews
|
11
11
|
|
12
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
13
|
down and generate only 3 SQL insert statements - one for the publishers, one for the books, and one for the reviews.
|
data/benchmarks/benchmark.rb
CHANGED
@@ -1,45 +1,48 @@
|
|
1
|
-
require
|
2
|
-
|
3
|
-
require
|
1
|
+
require 'pathname'
|
2
|
+
require "fileutils"
|
3
|
+
require "active_record"
|
4
4
|
|
5
|
-
|
6
|
-
options = BenchmarkOptionParser.parse( ARGV )
|
5
|
+
benchmark_dir = File.dirname(__FILE__)
|
7
6
|
|
8
|
-
#
|
9
|
-
|
10
|
-
SUPPORT_DIR = this_dir.join('../test')
|
7
|
+
# Get the gem into the load path
|
8
|
+
$LOAD_PATH.unshift(File.join(benchmark_dir, '..', 'lib'))
|
11
9
|
|
12
|
-
# Load the
|
13
|
-
|
10
|
+
# Load the benchmark files
|
11
|
+
Dir[File.join( benchmark_dir, 'lib', '*.rb' ) ].sort.each{ |f| require f }
|
14
12
|
|
15
|
-
#
|
16
|
-
|
17
|
-
require LIB_DIR.join("activerecord-import/#{adapter}")
|
13
|
+
# Parse the options passed in via the command line
|
14
|
+
options = BenchmarkOptionParser.parse( ARGV )
|
18
15
|
|
16
|
+
FileUtils.mkdir_p 'log'
|
17
|
+
ActiveRecord::Base.configurations["test"] = YAML.load_file(File.join(benchmark_dir, "../test/database.yml"))[options.adapter]
|
19
18
|
ActiveRecord::Base.logger = Logger.new("log/test.log")
|
20
19
|
ActiveRecord::Base.logger.level = Logger::DEBUG
|
21
|
-
ActiveRecord::Base.
|
22
|
-
|
20
|
+
ActiveRecord::Base.default_timezone = :utc
|
21
|
+
|
22
|
+
require "activerecord-import"
|
23
|
+
ActiveRecord::Base.establish_connection(:test)
|
23
24
|
|
24
25
|
ActiveSupport::Notifications.subscribe(/active_record.sql/) do |event, _, _, _, hsh|
|
25
26
|
ActiveRecord::Base.logger.info hsh[:sql]
|
26
27
|
end
|
27
28
|
|
28
|
-
|
29
|
+
# Load base/generic schema
|
30
|
+
require File.join(benchmark_dir, "../test/schema/version")
|
31
|
+
require File.join(benchmark_dir, "../test/schema/generic_schema")
|
32
|
+
adapter_schema = File.join(benchmark_dir, "schema/#{options.adapter}_schema.rb")
|
29
33
|
require adapter_schema if File.exists?(adapter_schema)
|
30
|
-
Dir[this_dir.join("models/*.rb")].each{ |file| require file }
|
31
34
|
|
32
|
-
|
33
|
-
|
35
|
+
Dir[File.dirname(__FILE__) + "/models/*.rb"].each{ |file| require file }
|
36
|
+
|
37
|
+
|
38
|
+
require File.join( benchmark_dir, 'lib', "#{options.adapter}_benchmark" )
|
34
39
|
|
35
|
-
# TODO implement method/table-type selection
|
36
40
|
table_types = nil
|
37
41
|
if options.benchmark_all_types
|
38
42
|
table_types = [ "all" ]
|
39
43
|
else
|
40
44
|
table_types = options.table_types.keys
|
41
45
|
end
|
42
|
-
puts
|
43
46
|
|
44
47
|
letter = options.adapter[0].chr
|
45
48
|
clazz_str = letter.upcase + options.adapter[1..-1].downcase
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'ostruct'
|
3
3
|
|
4
|
-
#
|
4
|
+
#
|
5
5
|
# == PARAMETERS
|
6
6
|
# * a - database adapter. ie: mysql, postgresql, oracle, etc.
|
7
7
|
# * n - number of objects to test with. ie: 1, 100, 1000, etc.
|
@@ -13,12 +13,12 @@ module BenchmarkOptionParser
|
|
13
13
|
def self.print_banner
|
14
14
|
puts BANNER
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def self.print_banner!
|
18
18
|
print_banner
|
19
19
|
exit
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
def self.print_options( options )
|
23
23
|
puts "Benchmarking the following options:"
|
24
24
|
puts " Database adapter: #{options.adapter}"
|
@@ -26,7 +26,7 @@ module BenchmarkOptionParser
|
|
26
26
|
puts " Table types:"
|
27
27
|
print_valid_table_types( options, :prefix=>" " )
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
# TODO IMPLEMENT THIS
|
31
31
|
def self.print_valid_table_types( options, hsh={:prefix=>''} )
|
32
32
|
if options.table_types.keys.size > 0
|
@@ -35,36 +35,37 @@ module BenchmarkOptionParser
|
|
35
35
|
puts 'No table types defined.'
|
36
36
|
end
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
def self.parse( args )
|
40
|
-
options = OpenStruct.new(
|
41
|
-
:
|
40
|
+
options = OpenStruct.new(
|
41
|
+
:adapter => 'mysql',
|
42
|
+
:table_types => {},
|
42
43
|
:delete_on_finish => true,
|
43
44
|
:number_of_objects => [],
|
44
45
|
:outputs => [] )
|
45
|
-
|
46
|
+
|
46
47
|
opts = OptionParser.new do |opts|
|
47
48
|
opts.banner = BANNER
|
48
|
-
|
49
|
+
|
49
50
|
# parse the database adapter
|
50
|
-
opts.on( "a", "--adapter [String]",
|
51
|
+
opts.on( "a", "--adapter [String]",
|
51
52
|
"The database adapter to use. IE: mysql, postgresql, oracle" ) do |arg|
|
52
53
|
options.adapter = arg
|
53
54
|
end
|
54
|
-
|
55
|
+
|
55
56
|
# parse do_not_delete flag
|
56
|
-
opts.on( "d", "--do-not-delete",
|
57
|
+
opts.on( "d", "--do-not-delete",
|
57
58
|
"By default all records in the benchmark tables will be deleted at the end of the benchmark. " +
|
58
59
|
"This flag indicates not to delete the benchmark data." ) do |arg|
|
59
60
|
options.delete_on_finish = false
|
60
61
|
end
|
61
|
-
|
62
|
+
|
62
63
|
# parse the number of row objects to test
|
63
64
|
opts.on( "n", "--num [Integer]",
|
64
65
|
"The number of objects to benchmark." ) do |arg|
|
65
66
|
options.number_of_objects << arg.to_i
|
66
67
|
end
|
67
|
-
|
68
|
+
|
68
69
|
# parse the table types to test
|
69
70
|
opts.on( "t", "--table-type [String]",
|
70
71
|
"The table type to test. This can be used multiple times." ) do |arg|
|
@@ -72,7 +73,7 @@ module BenchmarkOptionParser
|
|
72
73
|
options.table_types['all'] = options.benchmark_all_types = true
|
73
74
|
else
|
74
75
|
options.table_types[arg] = true
|
75
|
-
end
|
76
|
+
end
|
76
77
|
end
|
77
78
|
|
78
79
|
# print results in CSV format
|
@@ -86,7 +87,7 @@ module BenchmarkOptionParser
|
|
86
87
|
end
|
87
88
|
end #end opt.parse!
|
88
89
|
|
89
|
-
begin
|
90
|
+
begin
|
90
91
|
opts.parse!( args )
|
91
92
|
if options.table_types.size == 0
|
92
93
|
options.table_types['all'] = options.benchmark_all_types = true
|
@@ -94,9 +95,12 @@ module BenchmarkOptionParser
|
|
94
95
|
rescue Exception => ex
|
95
96
|
print_banner!
|
96
97
|
end
|
97
|
-
|
98
|
+
|
99
|
+
options.number_of_objects = [1000] if options.number_of_objects.empty?
|
100
|
+
options.outputs = [ OpenStruct.new( :format => 'html', :filename => 'benchmark.html')] if options.outputs.empty?
|
101
|
+
|
98
102
|
print_options( options )
|
99
|
-
|
103
|
+
|
100
104
|
options
|
101
105
|
end
|
102
106
|
|
@@ -1,12 +1,12 @@
|
|
1
|
-
require '
|
1
|
+
require 'csv'
|
2
2
|
|
3
3
|
module OutputToCSV
|
4
4
|
def self.output_results( filename, results )
|
5
|
-
|
5
|
+
CSV.open( filename, 'w' ) do |csv|
|
6
6
|
# Iterate over each result set, which contains many results
|
7
7
|
results.each do |result_set|
|
8
8
|
columns, times = [], []
|
9
|
-
result_set.each do |result|
|
9
|
+
result_set.each do |result|
|
10
10
|
columns << result.description
|
11
11
|
times << result.tms.real
|
12
12
|
end
|
data/gemfiles/3.1.gemfile
CHANGED
data/gemfiles/3.2.gemfile
CHANGED
data/gemfiles/4.0.gemfile
CHANGED
data/gemfiles/4.1.gemfile
CHANGED
data/gemfiles/4.2.gemfile
CHANGED
data/lib/activerecord-import.rb
CHANGED
@@ -1,17 +1,16 @@
|
|
1
|
-
|
2
|
-
class
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
1
|
+
ActiveSupport.on_load(:active_record) do
|
2
|
+
class ActiveRecord::Base
|
3
|
+
class << self
|
4
|
+
def establish_connection_with_activerecord_import(*args)
|
5
|
+
conn = establish_connection_without_activerecord_import(*args)
|
6
|
+
if !ActiveRecord.const_defined?(:Import) || !ActiveRecord::Import.respond_to?(:load_from_connection_pool)
|
7
|
+
require "activerecord-import/base"
|
8
|
+
end
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
ActiveRecord::Import.load_from_connection_pool connection_pool
|
11
|
+
conn
|
12
|
+
end
|
13
|
+
alias_method_chain :establish_connection, :activerecord_import
|
14
|
+
end
|
14
15
|
end
|
15
|
-
|
16
|
-
ActiveRecord::Import.load_from_connection_pool connection_pool
|
17
16
|
end
|
@@ -236,7 +236,7 @@ class ActiveRecord::Base
|
|
236
236
|
# This returns an object which responds to +failed_instances+ and +num_inserts+.
|
237
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.
|
238
238
|
# * num_inserts - the number of insert statements it took to import the data
|
239
|
-
# * ids - the
|
239
|
+
# * ids - the primary keys of the imported ids, if the adpater supports it, otherwise and empty array.
|
240
240
|
def import(*args)
|
241
241
|
if args.first.is_a?( Array ) and args.first.first.is_a? ActiveRecord::Base
|
242
242
|
options = {}
|
@@ -253,6 +253,11 @@ class ActiveRecord::Base
|
|
253
253
|
options = { :validate=>true, :timestamps=>true, :primary_key=>primary_key }
|
254
254
|
options.merge!( args.pop ) if args.last.is_a? Hash
|
255
255
|
|
256
|
+
# Don't modify incoming arguments
|
257
|
+
if options[:on_duplicate_key_update]
|
258
|
+
options[:on_duplicate_key_update] = options[:on_duplicate_key_update].dup
|
259
|
+
end
|
260
|
+
|
256
261
|
is_validating = options[:validate]
|
257
262
|
is_validating = true unless options[:validate_with_context].nil?
|
258
263
|
|
@@ -285,6 +290,7 @@ class ActiveRecord::Base
|
|
285
290
|
end
|
286
291
|
|
287
292
|
# dup the passed in array so we don't modify it unintentionally
|
293
|
+
column_names = column_names.dup
|
288
294
|
array_of_attributes = array_of_attributes.dup
|
289
295
|
|
290
296
|
# Force the primary key col into the insert if it's not
|
@@ -349,6 +355,7 @@ class ActiveRecord::Base
|
|
349
355
|
instance = new do |model|
|
350
356
|
hsh.each_pair{ |k,v| model.send("#{k}=", v) }
|
351
357
|
end
|
358
|
+
|
352
359
|
if not instance.valid?(options[:validate_with_context])
|
353
360
|
array_of_attributes[ i ] = nil
|
354
361
|
failed_instances << instance
|
@@ -376,8 +383,13 @@ class ActiveRecord::Base
|
|
376
383
|
|
377
384
|
unless scope_columns.blank?
|
378
385
|
scope_columns.zip(scope_values).each do |name, value|
|
379
|
-
|
380
|
-
|
386
|
+
name_as_sym = name.to_sym
|
387
|
+
next if column_names.include?(name_as_sym)
|
388
|
+
|
389
|
+
is_sti = (name_as_sym == inheritance_column.to_sym && self < base_class)
|
390
|
+
value = value.first if is_sti
|
391
|
+
|
392
|
+
column_names << name_as_sym
|
381
393
|
array_of_attributes.each { |attrs| attrs << value }
|
382
394
|
end
|
383
395
|
end
|
@@ -417,8 +429,14 @@ class ActiveRecord::Base
|
|
417
429
|
def set_ids_and_mark_clean(models, import_result)
|
418
430
|
unless models.nil?
|
419
431
|
import_result.ids.each_with_index do |id, index|
|
420
|
-
models[index]
|
421
|
-
|
432
|
+
model = models[index]
|
433
|
+
model.id = id.to_i
|
434
|
+
if model.respond_to?(:clear_changes_information) # Rails 4.0 and higher
|
435
|
+
model.clear_changes_information
|
436
|
+
else # Rails 3.1
|
437
|
+
model.instance_variable_get(:@changed_attributes).clear
|
438
|
+
end
|
439
|
+
model.instance_variable_set(:@new_record, false)
|
422
440
|
end
|
423
441
|
end
|
424
442
|
end
|
@@ -473,10 +491,12 @@ class ActiveRecord::Base
|
|
473
491
|
if val.nil? && column.name == primary_key && !sequence_name.blank?
|
474
492
|
connection_memo.next_value_for_sequence(sequence_name)
|
475
493
|
elsif column
|
476
|
-
if
|
494
|
+
if respond_to?(:type_caster) && type_caster.respond_to?(:type_cast_for_database) # Rails 5.0 and higher
|
495
|
+
connection_memo.quote(type_caster.type_cast_for_database(column.name, val))
|
496
|
+
elsif column.respond_to?(:type_cast_from_user) # Rails 4.2 and higher
|
477
497
|
connection_memo.quote(column.type_cast_from_user(val), column)
|
478
|
-
else
|
479
|
-
connection_memo.quote(column.type_cast(val), column)
|
498
|
+
else # Rails 3.1, 3.2, and 4.1
|
499
|
+
connection_memo.quote(column.type_cast(val), column)
|
480
500
|
end
|
481
501
|
end
|
482
502
|
end
|
@@ -509,7 +529,7 @@ class ActiveRecord::Base
|
|
509
529
|
array_of_attributes.each { |arr| arr << value }
|
510
530
|
end
|
511
531
|
|
512
|
-
if supports_on_duplicate_key_update?
|
532
|
+
if supports_on_duplicate_key_update? and options[:on_duplicate_key_update] != false
|
513
533
|
if options[:on_duplicate_key_update]
|
514
534
|
options[:on_duplicate_key_update] << key.to_sym if options[:on_duplicate_key_update].is_a?(Array) && !options[:on_duplicate_key_update].include?(key.to_sym)
|
515
535
|
options[:on_duplicate_key_update][key.to_sym] = key.to_sym if options[:on_duplicate_key_update].is_a?(Hash)
|
@@ -40,8 +40,8 @@ module ActiveRecord # :nodoc:
|
|
40
40
|
end
|
41
41
|
|
42
42
|
if matched_instance
|
43
|
-
instance.clear_aggregation_cache
|
44
|
-
instance.clear_association_cache
|
43
|
+
instance.send :clear_aggregation_cache
|
44
|
+
instance.send :clear_association_cache
|
45
45
|
instance.instance_variable_set :@attributes, matched_instance.instance_variable_get(:@attributes)
|
46
46
|
|
47
47
|
if instance.respond_to?(:clear_changes_information)
|
data/test/import_test.rb
CHANGED
@@ -19,6 +19,22 @@ describe "#import" do
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
+
describe "argument safety" do
|
23
|
+
it "should not modify the passed in columns array" do
|
24
|
+
assert_nothing_raised do
|
25
|
+
columns = %w(title author_name).freeze
|
26
|
+
Topic.import columns, [["foo", "bar"]]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should not modify the passed in values array" do
|
31
|
+
assert_nothing_raised do
|
32
|
+
values = [["foo", "bar"]].freeze
|
33
|
+
Topic.import %w(title author_name), values
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
22
38
|
describe "with non-default ActiveRecord models" do
|
23
39
|
context "that have a non-standard primary key (that is no sequence)" do
|
24
40
|
it "should import models successfully" do
|
@@ -139,7 +155,11 @@ describe "#import" do
|
|
139
155
|
|
140
156
|
it "doesn't reload any data (doesn't work)" do
|
141
157
|
Topic.import new_topics, :synchronize => new_topics
|
142
|
-
|
158
|
+
if Topic.support_setting_primary_key_of_imported_objects?
|
159
|
+
assert new_topics.all?(&:persisted?), "Records should have been reloaded"
|
160
|
+
else
|
161
|
+
assert new_topics.all?(&:new_record?), "No record should have been reloaded"
|
162
|
+
end
|
143
163
|
end
|
144
164
|
end
|
145
165
|
|
@@ -374,8 +394,14 @@ describe "#import" do
|
|
374
394
|
]
|
375
395
|
Book.import books
|
376
396
|
assert_equal 2, Book.count
|
377
|
-
|
378
|
-
|
397
|
+
|
398
|
+
if ENV['AR_VERSION'].to_i >= 5.0
|
399
|
+
assert_equal 'draft', Book.first.read_attribute('status')
|
400
|
+
assert_equal 'published', Book.last.read_attribute('status')
|
401
|
+
else
|
402
|
+
assert_equal 0, Book.first.read_attribute('status')
|
403
|
+
assert_equal 1, Book.last.read_attribute('status')
|
404
|
+
end
|
379
405
|
end
|
380
406
|
|
381
407
|
if ENV['AR_VERSION'].to_i > 4.1
|
@@ -387,8 +413,14 @@ describe "#import" do
|
|
387
413
|
]
|
388
414
|
Book.import books
|
389
415
|
assert_equal 2, Book.count
|
390
|
-
|
391
|
-
|
416
|
+
|
417
|
+
if ENV['AR_VERSION'].to_i >= 5.0
|
418
|
+
assert_equal 'draft', Book.first.read_attribute('status')
|
419
|
+
assert_equal 'published', Book.last.read_attribute('status')
|
420
|
+
else
|
421
|
+
assert_equal 0, Book.first.read_attribute('status')
|
422
|
+
assert_equal 1, Book.last.read_attribute('status')
|
423
|
+
end
|
392
424
|
end
|
393
425
|
end
|
394
426
|
end
|
@@ -419,5 +451,14 @@ describe "#import" do
|
|
419
451
|
end
|
420
452
|
assert_equal({:a => :b}, Widget.find_by_w_id(1).data)
|
421
453
|
end
|
454
|
+
|
455
|
+
requires_active_record_version ">= 4" do
|
456
|
+
it "imports values for serialized JSON fields" do
|
457
|
+
assert_difference "Widget.unscoped.count", +1 do
|
458
|
+
Widget.import [:w_id, :json_data], [[9, {:a => :b}]]
|
459
|
+
end
|
460
|
+
assert_equal({:a => :b}.as_json, Widget.find_by_w_id(9).json_data)
|
461
|
+
end
|
462
|
+
end
|
422
463
|
end
|
423
464
|
end
|
data/test/models/widget.rb
CHANGED
@@ -3,6 +3,12 @@ class ActiveSupport::TestCase
|
|
3
3
|
self.use_transactional_fixtures = true
|
4
4
|
|
5
5
|
class << self
|
6
|
+
def requires_active_record_version(version_string, &blk)
|
7
|
+
if Gem::Dependency.new("expected",version_string).match?("actual", ActiveRecord::VERSION::STRING)
|
8
|
+
instance_eval(&blk)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
6
12
|
def assertion(name, &block)
|
7
13
|
mc = class << self ; self ; end
|
8
14
|
mc.class_eval do
|
@@ -0,0 +1,13 @@
|
|
1
|
+
if defined?(EM::Synchrony) && ActiveRecord::VERSION::STRING >= "4.0"
|
2
|
+
module EM::Synchrony
|
3
|
+
module ActiveRecord
|
4
|
+
module Adapter
|
5
|
+
def reset_transaction
|
6
|
+
@transaction_manager = ::ActiveRecord::ConnectionAdapters::TransactionManager.new(self)
|
7
|
+
end
|
8
|
+
|
9
|
+
delegate :open_transactions, :current_transaction, :begin_transaction, :commit_transaction, :rollback_transaction, to: :transaction_manager
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -39,6 +39,15 @@ class ActiveSupport::TestCase
|
|
39
39
|
assert_equal "johndoe@example.com", updated_topic.author_email_address
|
40
40
|
end
|
41
41
|
|
42
|
+
assertion(:should_raise_update_fields_mentioned) do
|
43
|
+
assert_raise ActiveRecord::RecordNotUnique do
|
44
|
+
perform_import
|
45
|
+
end
|
46
|
+
|
47
|
+
assert_equal "Book", updated_topic.title
|
48
|
+
assert_equal "john@doe.com", updated_topic.author_email_address
|
49
|
+
end
|
50
|
+
|
42
51
|
assertion(:should_update_fields_mentioned_with_hash_mappings) do
|
43
52
|
perform_import
|
44
53
|
assert_equal "johndoe@example.com", updated_topic.title
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
def should_support_mysql_import_functionality
|
3
3
|
# Forcefully disable strict mode for this session.
|
4
|
-
ActiveRecord::Base.connection.execute "set sql_mode=''"
|
4
|
+
ActiveRecord::Base.connection.execute "set sql_mode='STRICT_ALL_TABLES'"
|
5
5
|
|
6
6
|
describe "#import with :on_duplicate_key_update option (mysql specific functionality)" do
|
7
7
|
extend ActiveSupport::TestCase::MySQLAssertions
|
@@ -16,6 +16,15 @@ def should_support_mysql_import_functionality
|
|
16
16
|
macro(:perform_import){ raise "supply your own #perform_import in a context below" }
|
17
17
|
macro(:updated_topic){ Topic.find(@topic.id) }
|
18
18
|
|
19
|
+
describe "argument safety" do
|
20
|
+
it "should not modify the passed in :on_duplicate_key_update columns array" do
|
21
|
+
assert_nothing_raised do
|
22
|
+
columns = %w(title author_name).freeze
|
23
|
+
Topic.import columns, [["foo", "bar"]], :on_duplicate_key_update => columns
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
19
28
|
context "given columns and values with :validation checks turned off" do
|
20
29
|
let(:columns){ %w( id title author_name author_email_address parent_id ) }
|
21
30
|
let(:values){ [ [ 99, "Book", "John Doe", "john@doe.com", 17 ] ] }
|
@@ -116,13 +125,33 @@ def should_support_mysql_import_functionality
|
|
116
125
|
should_update_fields_mentioned_with_hash_mappings
|
117
126
|
end
|
118
127
|
end
|
128
|
+
|
129
|
+
context "given array of model instances with :on_duplicate_key_update turned off" do
|
130
|
+
let(:columns){ %w( id title author_name author_email_address parent_id ) }
|
131
|
+
let(:values){ [ [ 100, "Book", "John Doe", "john@doe.com", 17 ] ] }
|
132
|
+
let(:updated_values){ [ [ 100, "Book - 2nd Edition", "This should raise an exception", "john@nogo.com", 57 ] ] }
|
133
|
+
|
134
|
+
macro(:perform_import) do |*opts|
|
135
|
+
# `:on_duplicate_key_update => false` is the tested feature
|
136
|
+
Topic.import columns, updated_values, opts.extract_options!.merge(:on_duplicate_key_update => false, :validate => false)
|
137
|
+
end
|
119
138
|
|
139
|
+
setup do
|
140
|
+
Topic.import columns, values, :validate => false
|
141
|
+
@topic = Topic.find 100
|
142
|
+
end
|
143
|
+
|
144
|
+
context "using string column names" do
|
145
|
+
let(:update_columns){ [ "title", "author_email_address", "parent_id" ] }
|
146
|
+
should_raise_update_fields_mentioned
|
147
|
+
end
|
148
|
+
end
|
120
149
|
end
|
121
150
|
|
122
151
|
describe "#import with :synchronization option" do
|
123
152
|
let(:topics){ Array.new }
|
124
|
-
let(:values){ [ [topics.first.id, "Jerry Carter"], [topics.last.id, "Chad Fowler"] ]}
|
125
|
-
let(:columns){ %W(id author_name) }
|
153
|
+
let(:values){ [ [topics.first.id, "Jerry Carter", "title1"], [topics.last.id, "Chad Fowler", "title2"] ]}
|
154
|
+
let(:columns){ %W(id author_name title) }
|
126
155
|
|
127
156
|
setup do
|
128
157
|
topics << Topic.create!(:title=>"LDAP", :author_name=>"Big Bird")
|
@@ -101,5 +101,29 @@ def should_support_postgresql_import_functionality
|
|
101
101
|
end
|
102
102
|
end
|
103
103
|
end
|
104
|
+
|
105
|
+
describe "with query cache enabled" do
|
106
|
+
setup do
|
107
|
+
unless ActiveRecord::Base.connection.query_cache_enabled
|
108
|
+
ActiveRecord::Base.connection.enable_query_cache!
|
109
|
+
@disable_cache_on_teardown = true
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
it "clears cache on insert" do
|
114
|
+
before_import = Topic.all.to_a
|
115
|
+
|
116
|
+
Topic.import(Build(2, :topics), validate: false)
|
117
|
+
|
118
|
+
after_import = Topic.all.to_a
|
119
|
+
assert_equal 2, after_import.size - before_import.size
|
120
|
+
end
|
121
|
+
|
122
|
+
teardown do
|
123
|
+
if @disable_cache_on_teardown
|
124
|
+
ActiveRecord::Base.connection.disable_query_cache!
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
104
128
|
end
|
105
129
|
end
|
data/test/test_helper.rb
CHANGED
@@ -10,6 +10,8 @@ ENV["RAILS_ENV"] = "test"
|
|
10
10
|
require "bundler"
|
11
11
|
Bundler.setup
|
12
12
|
|
13
|
+
require 'pry'
|
14
|
+
|
13
15
|
require "active_record"
|
14
16
|
require "active_record/fixtures"
|
15
17
|
require "active_support/test_case"
|
@@ -34,7 +36,7 @@ ActiveRecord::Base.configurations["test"] = YAML.load_file(test_dir.join("databa
|
|
34
36
|
ActiveRecord::Base.default_timezone = :utc
|
35
37
|
|
36
38
|
require "activerecord-import"
|
37
|
-
ActiveRecord::Base.establish_connection
|
39
|
+
ActiveRecord::Base.establish_connection :test
|
38
40
|
|
39
41
|
ActiveSupport::Notifications.subscribe(/active_record.sql/) do |event, _, _, _, hsh|
|
40
42
|
ActiveRecord::Base.logger.info hsh[:sql]
|
data/test/travis/build.sh
CHANGED
@@ -7,12 +7,12 @@ function run {
|
|
7
7
|
$@
|
8
8
|
}
|
9
9
|
|
10
|
-
for activerecord_version in "3.1" "3.2" "4.1" "4.2" ; do
|
10
|
+
for activerecord_version in "3.1" "3.2" "4.0" "4.1" "4.2" "5.0" ; do
|
11
11
|
export AR_VERSION=$activerecord_version
|
12
12
|
|
13
13
|
bundle update activerecord
|
14
14
|
|
15
|
-
run
|
15
|
+
run bundle exec rake test:em_mysql2 # Run tests for em_mysql2
|
16
16
|
run bundle exec rake test:mysql # Run tests for mysql
|
17
17
|
run bundle exec rake test:mysql2 # Run tests for mysql2
|
18
18
|
run bundle exec rake test:mysql2spatial # Run tests for mysql2spatial
|
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.11.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: 2016-01-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -56,7 +56,6 @@ files:
|
|
56
56
|
- activerecord-import.gemspec
|
57
57
|
- benchmarks/README
|
58
58
|
- benchmarks/benchmark.rb
|
59
|
-
- benchmarks/boot.rb
|
60
59
|
- benchmarks/lib/base.rb
|
61
60
|
- benchmarks/lib/cli_parser.rb
|
62
61
|
- benchmarks/lib/float.rb
|
@@ -72,6 +71,7 @@ files:
|
|
72
71
|
- gemfiles/4.0.gemfile
|
73
72
|
- gemfiles/4.1.gemfile
|
74
73
|
- gemfiles/4.2.gemfile
|
74
|
+
- gemfiles/5.0.gemfile
|
75
75
|
- lib/activerecord-import.rb
|
76
76
|
- lib/activerecord-import/active_record/adapters/abstract_adapter.rb
|
77
77
|
- lib/activerecord-import/active_record/adapters/em_mysql2_adapter.rb
|
@@ -132,6 +132,7 @@ files:
|
|
132
132
|
- test/schema/version.rb
|
133
133
|
- test/sqlite3/import_test.rb
|
134
134
|
- test/support/active_support/test_case_extensions.rb
|
135
|
+
- test/support/em-synchrony_extensions.rb
|
135
136
|
- test/support/factories.rb
|
136
137
|
- test/support/generate.rb
|
137
138
|
- test/support/mysql/assertions.rb
|
@@ -163,7 +164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
163
164
|
version: '0'
|
164
165
|
requirements: []
|
165
166
|
rubyforge_project:
|
166
|
-
rubygems_version: 2.4.
|
167
|
+
rubygems_version: 2.4.5.1
|
167
168
|
signing_key:
|
168
169
|
specification_version: 4
|
169
170
|
summary: Bulk-loading extension for ActiveRecord
|
@@ -202,6 +203,7 @@ test_files:
|
|
202
203
|
- test/schema/version.rb
|
203
204
|
- test/sqlite3/import_test.rb
|
204
205
|
- test/support/active_support/test_case_extensions.rb
|
206
|
+
- test/support/em-synchrony_extensions.rb
|
205
207
|
- test/support/factories.rb
|
206
208
|
- test/support/generate.rb
|
207
209
|
- test/support/mysql/assertions.rb
|
@@ -213,4 +215,3 @@ test_files:
|
|
213
215
|
- test/travis/database.yml
|
214
216
|
- test/value_sets_bytes_parser_test.rb
|
215
217
|
- test/value_sets_records_parser_test.rb
|
216
|
-
has_rdoc:
|
data/benchmarks/boot.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
begin ; require 'rubygems' ; rescue LoadError ; end
|
2
|
-
require 'active_record' # ActiveRecord loads the Benchmark library automatically
|
3
|
-
require 'active_record/version'
|
4
|
-
require 'fastercsv'
|
5
|
-
require 'fileutils'
|
6
|
-
require 'logger'
|
7
|
-
|
8
|
-
# Files are loaded alphabetically. If this is a problem then manually specify the files
|
9
|
-
# that need to be loaded here.
|
10
|
-
Dir[ File.join( File.dirname( __FILE__ ), 'lib', '*.rb' ) ].sort.each{ |f| require f }
|
11
|
-
|
12
|
-
ActiveRecord::Base.logger = Logger.new STDOUT
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|