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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eb77d17b829e2ac21c8816b01c3d365b4f9ddc6e
4
- data.tar.gz: 301500da2b010aaac8473af8c5436124c59c0ca0
3
+ metadata.gz: a877605fbbb5a9a7100063ad764a46d8a497708c
4
+ data.tar.gz: 4eee2b275df655fb6f63f47de0b1b60ae669629c
5
5
  SHA512:
6
- metadata.gz: 242c10dbb23c6236cd2e2f10328a6c63e567c3fa55bb899c05771b37c7bc3507632495779deffdf52dcd8798c7785398be1493330d32f69953a20d3e0a1348be
7
- data.tar.gz: 1c8eccfa18fef779eb2c6c86cde365cd6254efc453b09734e19ceafe62d93af445de0042e0ef6d840f7c7e9e5546f93432ff2a3fe9bde3f4a97d6319031be2f5
6
+ metadata.gz: f3457053406910b5e2d4d740ddb5dc2efa716f7ef8e54596998aec575568e62ebfa55570c46270c837bdc1f3c1e145e1ff954441ce58f881343f44cb9ea01be3
7
+ data.tar.gz: ae534e1dd917a93dc3c0907bba45e7d1e6a6fb553e1a3a86c08ab8d85cb019c322d70a7cc6c90e7c6b25367b059a730e2c6d886336abc7118d89e8fc22b253d4
@@ -1,7 +1,7 @@
1
1
  language: ruby
2
2
  cache: bundler
3
3
  rvm:
4
- - 2.0.0
4
+ - 2.2.3
5
5
 
6
6
  gemfile:
7
7
  - Gemfile
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-ruby", "~> 1.3.1"
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'] || "3.2"
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__)
@@ -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.
@@ -1,45 +1,48 @@
1
- require "pathname"
2
- this_dir = Pathname.new File.dirname(__FILE__)
3
- require this_dir.join('boot')
1
+ require 'pathname'
2
+ require "fileutils"
3
+ require "active_record"
4
4
 
5
- # Parse the options passed in via the command line
6
- options = BenchmarkOptionParser.parse( ARGV )
5
+ benchmark_dir = File.dirname(__FILE__)
7
6
 
8
- # The support directory where we use to load our connections and models for the
9
- # benchmarks.
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 database adapter
13
- adapter = options.adapter
10
+ # Load the benchmark files
11
+ Dir[File.join( benchmark_dir, 'lib', '*.rb' ) ].sort.each{ |f| require f }
14
12
 
15
- # load the library
16
- LIB_DIR = this_dir.join("../lib")
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.configurations["test"] = YAML.load(SUPPORT_DIR.join("database.yml").open)[adapter]
22
- ActiveRecord::Base.establish_connection "test"
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
- adapter_schema = SUPPORT_DIR.join("schema/#{adapter}_schema.rb")
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
- # Load databse specific benchmarks
33
- require File.join( File.dirname( __FILE__ ), 'lib', "#{adapter}_benchmark" )
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
- :table_types => {},
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 'fastercsv'
1
+ require 'csv'
2
2
 
3
3
  module OutputToCSV
4
4
  def self.output_results( filename, results )
5
- FasterCSV.open( filename, 'w' ) do |csv|
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
@@ -1,3 +1,3 @@
1
1
  class TestInnoDb < ActiveRecord::Base
2
- set_table_name 'test_innodb'
2
+ self.table_name = 'test_innodb'
3
3
  end
@@ -1,3 +1,3 @@
1
1
  class TestMemory < ActiveRecord::Base
2
- set_table_name 'test_memory'
2
+ self.table_name = 'test_memory'
3
3
  end
@@ -1,3 +1,3 @@
1
1
  class TestMyISAM < ActiveRecord::Base
2
- set_table_name 'test_myisam'
2
+ self.table_name = 'test_myisam'
3
3
  end
@@ -1,4 +1,5 @@
1
1
  platforms :ruby do
2
2
  gem 'mysql', '>= 2.8.1'
3
3
  gem 'activerecord', '~> 3.1.0'
4
+ gem 'em-synchrony', '1.0.3'
4
5
  end
@@ -1,4 +1,5 @@
1
1
  platforms :ruby do
2
2
  gem 'mysql', '>= 2.8.1'
3
3
  gem 'activerecord', '~> 3.2.0'
4
+ gem 'em-synchrony', '1.0.3'
4
5
  end
@@ -1,4 +1,5 @@
1
1
  platforms :ruby do
2
2
  gem 'mysql', '~> 2.9'
3
- gem 'activerecord', '~> 4.0.0.rc2'
3
+ gem 'activerecord', '~> 4.0'
4
+ gem 'em-synchrony', '1.0.3'
4
5
  end
@@ -1,4 +1,5 @@
1
1
  platforms :ruby do
2
2
  gem 'mysql', '~> 2.9'
3
3
  gem 'activerecord', '~> 4.1'
4
+ gem 'em-synchrony', '1.0.4'
4
5
  end
@@ -1,4 +1,5 @@
1
1
  platforms :ruby do
2
2
  gem 'mysql', '~> 2.9'
3
3
  gem 'activerecord', '~> 4.2'
4
+ gem 'em-synchrony', '1.0.4'
4
5
  end
@@ -0,0 +1,5 @@
1
+ platforms :ruby do
2
+ gem 'mysql', '~> 2.9'
3
+ gem 'activerecord', '~> 5.0.0.beta1'
4
+ gem 'em-synchrony', '1.0.4'
5
+ end
@@ -1,17 +1,16 @@
1
- class ActiveRecord::Base
2
- class << self
3
- def establish_connection_with_activerecord_import(*args)
4
- establish_connection_without_activerecord_import(*args)
5
- ActiveSupport.run_load_hooks(:active_record_connection_established, connection_pool)
6
- end
7
- alias_method_chain :establish_connection, :activerecord_import
8
- end
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
- ActiveSupport.on_load(:active_record_connection_established) do |connection_pool|
12
- if !ActiveRecord.const_defined?(:Import, false) || !ActiveRecord::Import.respond_to?(:load_from_connection_pool)
13
- require "activerecord-import/base"
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
@@ -13,6 +13,8 @@ module ActiveRecord::Import::PostgreSQLAdapter
13
13
  sql2insert = base_sql + values.join( ',' ) + post_sql
14
14
  ids = select_values( sql2insert, *args )
15
15
 
16
+ ActiveRecord::Base.connection.query_cache.clear
17
+
16
18
  [number_of_inserts,ids]
17
19
  end
18
20
 
@@ -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 priamry keys of the imported ids, if the adpater supports it, otherwise and empty array.
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
- next if column_names.include?(name.to_sym)
380
- column_names << name.to_sym
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].id = id.to_i
421
- models[index].instance_variable_get(:@changed_attributes).clear # mark the model as saved
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 column.respond_to?(:type_cast_from_user) # Rails 4.2 and higher
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) # Rails 3.1, 3.2, and 4.1
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)
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Import
3
- VERSION = "0.10.0"
3
+ VERSION = "0.11.0"
4
4
  end
5
5
  end
@@ -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
- assert new_topics.all?(&:new_record?), "No record should have been reloaded"
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
- assert_equal 0, Book.first.read_attribute('status')
378
- assert_equal 1, Book.last.read_attribute('status')
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
- assert_equal 0, Book.first.read_attribute('status')
391
- assert_equal 1, Book.last.read_attribute('status')
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
@@ -4,4 +4,5 @@ class Widget < ActiveRecord::Base
4
4
  default_scope lambda { where(active: true) }
5
5
 
6
6
  serialize :data, Hash
7
- end
7
+ serialize :json_data, JSON
8
+ end
@@ -116,5 +116,6 @@ ActiveRecord::Schema.define do
116
116
  t.integer :w_id
117
117
  t.boolean :active, :default => false
118
118
  t.text :data
119
+ t.text :json_data
119
120
  end
120
121
  end
@@ -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
@@ -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 "test"
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]
@@ -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 run bundle exec rake test:em_mysql2 # Run tests for em_mysql2
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.10.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: 2015-07-08 00:00:00.000000000 Z
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.3
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:
@@ -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
-