activerecord-import 0.10.0 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://travis-ci.org/zdennis/activerecord-import.svg?branch=master)](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
|
-
|