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 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
-