activerecord-import 0.17.0 → 0.17.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +1 -4
- data/README.markdown +1 -1
- data/activerecord-import.gemspec +2 -2
- data/lib/activerecord-import/adapters/abstract_adapter.rb +2 -2
- data/lib/activerecord-import/adapters/mysql_adapter.rb +1 -1
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +17 -22
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +1 -1
- data/lib/activerecord-import/import.rb +18 -3
- data/lib/activerecord-import/version.rb +1 -1
- data/test/import_test.rb +24 -6
- data/test/schema/generic_schema.rb +1 -1
- data/test/support/active_support/test_case_extensions.rb +6 -1
- data/test/support/postgresql/import_examples.rb +1 -1
- data/test/support/shared_examples/recursive_import.rb +15 -0
- metadata +4 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d3e8c2443ad40d5d2a2b22c304ebc88780834b0b
|
4
|
+
data.tar.gz: f55f0a5b40c6ba2dee599c13a2c2de83eeeefc6b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 967eb0e53dd4bb1c8267263788d9dea872f8c4f431dfc5516d819fad1cbc5c6bae20000bf0bb29c6c045012c8f61cdfd93232e5f46a34ef78216247a179ae811
|
7
|
+
data.tar.gz: 73f3dd915a1ad43f22d0d0cc03dc7ccc717191180e05cbce8d35ddbe91ea3a747730f655443cfac34a8aae036a2117bf54bd347c6fbb3998193169fc9175dcdf
|
data/.travis.yml
CHANGED
@@ -18,6 +18,10 @@ matrix:
|
|
18
18
|
include:
|
19
19
|
- rvm: jruby-9.0.5.0
|
20
20
|
env: AR_VERSION=4.2
|
21
|
+
before_install:
|
22
|
+
- gem uninstall -i /home/travis/.rvm/gems/jruby-9.0.5.0@global bundler
|
23
|
+
- gem install bundler -v 1.13.7 --no-rdoc --no-ri --no-document
|
24
|
+
|
21
25
|
script:
|
22
26
|
- bundle exec rake test:jdbcsqlite3
|
23
27
|
- bundle exec rake test:jdbcmysql
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
## Changes in 0.17.1
|
2
|
+
|
3
|
+
### Fixes
|
4
|
+
|
5
|
+
* Along with setting id on models for adapters that support it,
|
6
|
+
add created_at and updated_at timestamps. Thanks to @jacob-carlborg
|
7
|
+
via \#364.
|
8
|
+
* Properly set returned ids when using composite_primary_keys.
|
9
|
+
Thanks to @guigs, @jkowens via \#371.
|
10
|
+
|
1
11
|
## Changes in 0.17.0
|
2
12
|
|
3
13
|
### New Features
|
data/Gemfile
CHANGED
@@ -4,6 +4,7 @@ gemspec
|
|
4
4
|
|
5
5
|
group :development, :test do
|
6
6
|
gem 'rubocop', '~> 0.38.0'
|
7
|
+
gem 'rake'
|
7
8
|
end
|
8
9
|
|
9
10
|
# Database Adapters
|
@@ -30,10 +31,6 @@ gem "mocha"
|
|
30
31
|
|
31
32
|
# Debugging
|
32
33
|
platforms :jruby do
|
33
|
-
gem "ruby-debug-base", "= 0.10.4"
|
34
|
-
end
|
35
|
-
|
36
|
-
platforms :jruby, :mri_18 do
|
37
34
|
gem "ruby-debug", "= 0.10.4"
|
38
35
|
end
|
39
36
|
|
data/README.markdown
CHANGED
@@ -41,7 +41,7 @@ Use the latest in the activerecord-import 0.3.x series.
|
|
41
41
|
|
42
42
|
Use activerecord-import 0.2.11. As of activerecord-import 0.3.0 we are relying on functionality that was introduced in Rails 3.1. Since Rails 3.0.x is no longer a supported version of Rails we have decided to drop support as well.
|
43
43
|
|
44
|
-
###
|
44
|
+
### More Information : Usage and Examples in Wiki
|
45
45
|
|
46
46
|
For more information on activerecord-import please see its wiki: https://github.com/zdennis/activerecord-import/wiki
|
47
47
|
|
data/activerecord-import.gemspec
CHANGED
@@ -4,8 +4,8 @@ require File.expand_path('../lib/activerecord-import/version', __FILE__)
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ["Zach Dennis"]
|
6
6
|
gem.email = ["zach.dennis@gmail.com"]
|
7
|
-
gem.summary = "Bulk
|
8
|
-
gem.description = "
|
7
|
+
gem.summary = "Bulk insert extension for ActiveRecord"
|
8
|
+
gem.description = "A library for bulk inserting data using ActiveRecord."
|
9
9
|
gem.homepage = "http://github.com/zdennis/activerecord-import"
|
10
10
|
gem.license = "Ruby"
|
11
11
|
|
@@ -4,7 +4,7 @@ module ActiveRecord::Import::AbstractAdapter
|
|
4
4
|
%(#{sequence_name}.nextval)
|
5
5
|
end
|
6
6
|
|
7
|
-
def insert_many( sql, values, *args ) # :nodoc:
|
7
|
+
def insert_many( sql, values, _options = {}, *args ) # :nodoc:
|
8
8
|
number_of_inserts = 1
|
9
9
|
|
10
10
|
base_sql, post_sql = if sql.is_a?( String )
|
@@ -45,7 +45,7 @@ module ActiveRecord::Import::AbstractAdapter
|
|
45
45
|
post_sql_statements = []
|
46
46
|
|
47
47
|
if supports_on_duplicate_key_update? && options[:on_duplicate_key_update]
|
48
|
-
post_sql_statements << sql_for_on_duplicate_key_update( table_name, options[:on_duplicate_key_update] )
|
48
|
+
post_sql_statements << sql_for_on_duplicate_key_update( table_name, options[:on_duplicate_key_update], options[:primary_key] )
|
49
49
|
elsif options[:on_duplicate_key_update]
|
50
50
|
logger.warn "Ignoring on_duplicate_key_update because it is not supported by the database."
|
51
51
|
end
|
@@ -7,7 +7,7 @@ module ActiveRecord::Import::MysqlAdapter
|
|
7
7
|
|
8
8
|
# +sql+ can be a single string or an array. If it is an array all
|
9
9
|
# elements that are in position >= 1 will be appended to the final SQL.
|
10
|
-
def insert_many( sql, values, *args ) # :nodoc:
|
10
|
+
def insert_many( sql, values, _options = {}, *args ) # :nodoc:
|
11
11
|
# the number of inserts default
|
12
12
|
number_of_inserts = 0
|
13
13
|
|
@@ -4,7 +4,8 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
4
4
|
|
5
5
|
MIN_VERSION_FOR_UPSERT = 90_500
|
6
6
|
|
7
|
-
def insert_many( sql, values, *args ) # :nodoc:
|
7
|
+
def insert_many( sql, values, options = {}, *args ) # :nodoc:
|
8
|
+
primary_key = options[:primary_key]
|
8
9
|
number_of_inserts = 1
|
9
10
|
ids = []
|
10
11
|
|
@@ -15,11 +16,17 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
15
16
|
end
|
16
17
|
|
17
18
|
sql2insert = base_sql + values.join( ',' ) + post_sql
|
18
|
-
|
19
|
-
|
20
|
-
query_cache.clear if query_cache_enabled
|
21
|
-
else
|
19
|
+
|
20
|
+
if primary_key.blank? || options[:no_returning]
|
22
21
|
insert( sql2insert, *args )
|
22
|
+
else
|
23
|
+
ids = if primary_key.is_a?( Array )
|
24
|
+
# Select composite primary keys
|
25
|
+
select_rows( sql2insert, *args )
|
26
|
+
else
|
27
|
+
select_values( sql2insert, *args )
|
28
|
+
end
|
29
|
+
query_cache.clear if query_cache_enabled
|
23
30
|
end
|
24
31
|
|
25
32
|
[number_of_inserts, ids]
|
@@ -45,7 +52,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
45
52
|
|
46
53
|
unless options[:no_returning] || options[:primary_key].blank?
|
47
54
|
primary_key = Array(options[:primary_key])
|
48
|
-
sql << " RETURNING
|
55
|
+
sql << " RETURNING #{primary_key.join(', ')}"
|
49
56
|
end
|
50
57
|
|
51
58
|
sql
|
@@ -76,7 +83,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
76
83
|
# Returns a generated ON CONFLICT DO UPDATE statement given the passed
|
77
84
|
# in +args+.
|
78
85
|
def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
|
79
|
-
arg = args
|
86
|
+
arg, primary_key = args
|
80
87
|
arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
|
81
88
|
return unless arg.is_a?( Hash )
|
82
89
|
|
@@ -88,7 +95,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
88
95
|
return sql << "#{conflict_target}DO NOTHING"
|
89
96
|
end
|
90
97
|
|
91
|
-
conflict_target ||= sql_for_default_conflict_target( table_name )
|
98
|
+
conflict_target ||= sql_for_default_conflict_target( table_name, primary_key )
|
92
99
|
unless conflict_target
|
93
100
|
raise ArgumentError, 'Expected :conflict_target or :constraint_name to be specified'
|
94
101
|
end
|
@@ -136,20 +143,8 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
136
143
|
end
|
137
144
|
end
|
138
145
|
|
139
|
-
def sql_for_default_conflict_target( table_name )
|
140
|
-
|
141
|
-
WITH pk_constraint AS (
|
142
|
-
SELECT conrelid, unnest(conkey) AS connum FROM pg_constraint
|
143
|
-
WHERE contype = 'p'
|
144
|
-
AND conrelid = #{quote(quote_table_name(table_name))}::regclass
|
145
|
-
), cons AS (
|
146
|
-
SELECT conrelid, connum, row_number() OVER() AS rownum FROM pk_constraint
|
147
|
-
)
|
148
|
-
SELECT attr.attname FROM pg_attribute attr
|
149
|
-
INNER JOIN cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.connum
|
150
|
-
ORDER BY cons.rownum
|
151
|
-
SQL
|
152
|
-
conflict_target = pks.join(', ')
|
146
|
+
def sql_for_default_conflict_target( table_name, primary_key )
|
147
|
+
conflict_target = Array(primary_key).join(', ')
|
153
148
|
"(#{conflict_target}) " if conflict_target.present?
|
154
149
|
end
|
155
150
|
|
@@ -17,7 +17,7 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
17
17
|
|
18
18
|
# +sql+ can be a single string or an array. If it is an array all
|
19
19
|
# elements that are in position >= 1 will be appended to the final SQL.
|
20
|
-
def insert_many(sql, values, *args) # :nodoc:
|
20
|
+
def insert_many( sql, values, _options = {}, *args ) # :nodoc:
|
21
21
|
number_of_inserts = 0
|
22
22
|
|
23
23
|
base_sql, post_sql = if sql.is_a?( String )
|
@@ -442,9 +442,11 @@ class ActiveRecord::Base
|
|
442
442
|
array_of_attributes.each { |a| a.concat(new_fields) }
|
443
443
|
end
|
444
444
|
|
445
|
+
timestamps = {}
|
446
|
+
|
445
447
|
# record timestamps unless disabled in ActiveRecord::Base
|
446
448
|
if record_timestamps && options.delete( :timestamps )
|
447
|
-
add_special_rails_stamps column_names, array_of_attributes, options
|
449
|
+
timestamps = add_special_rails_stamps column_names, array_of_attributes, options
|
448
450
|
end
|
449
451
|
|
450
452
|
return_obj = if is_validating
|
@@ -474,7 +476,7 @@ class ActiveRecord::Base
|
|
474
476
|
|
475
477
|
# if we have ids, then set the id on the models and mark the models as clean.
|
476
478
|
if models && support_setting_primary_key_of_imported_objects?
|
477
|
-
|
479
|
+
set_attributes_and_mark_clean(models, return_obj, timestamps)
|
478
480
|
|
479
481
|
# if there are auto-save associations on the models we imported that are new, import them as well
|
480
482
|
import_associations(models, options.dup) if options[:recursive]
|
@@ -575,6 +577,7 @@ class ActiveRecord::Base
|
|
575
577
|
# perform the inserts
|
576
578
|
result = connection.insert_many( [insert_sql, post_sql_statements].flatten,
|
577
579
|
batch_values,
|
580
|
+
options,
|
578
581
|
"#{self.class.name} Create Many Without Validations Or Callbacks" )
|
579
582
|
number_inserted += result[0]
|
580
583
|
ids += result[1]
|
@@ -592,7 +595,7 @@ class ActiveRecord::Base
|
|
592
595
|
|
593
596
|
private
|
594
597
|
|
595
|
-
def
|
598
|
+
def set_attributes_and_mark_clean(models, import_result, timestamps)
|
596
599
|
return if models.nil?
|
597
600
|
models -= import_result.failed_instances
|
598
601
|
import_result.ids.each_with_index do |id, index|
|
@@ -604,6 +607,10 @@ class ActiveRecord::Base
|
|
604
607
|
model.instance_variable_get(:@changed_attributes).clear
|
605
608
|
end
|
606
609
|
model.instance_variable_set(:@new_record, false)
|
610
|
+
|
611
|
+
timestamps.each do |attr, value|
|
612
|
+
model.send(attr + "=", value)
|
613
|
+
end
|
607
614
|
end
|
608
615
|
end
|
609
616
|
|
@@ -689,9 +696,13 @@ class ActiveRecord::Base
|
|
689
696
|
end
|
690
697
|
|
691
698
|
def add_special_rails_stamps( column_names, array_of_attributes, options )
|
699
|
+
timestamps = {}
|
700
|
+
|
692
701
|
AREXT_RAILS_COLUMNS[:create].each_pair do |key, blk|
|
693
702
|
next unless self.column_names.include?(key)
|
694
703
|
value = blk.call
|
704
|
+
timestamps[key] = value
|
705
|
+
|
695
706
|
index = column_names.index(key) || column_names.index(key.to_sym)
|
696
707
|
if index
|
697
708
|
# replace every instance of the array of attributes with our value
|
@@ -705,6 +716,8 @@ class ActiveRecord::Base
|
|
705
716
|
AREXT_RAILS_COLUMNS[:update].each_pair do |key, blk|
|
706
717
|
next unless self.column_names.include?(key)
|
707
718
|
value = blk.call
|
719
|
+
timestamps[key] = value
|
720
|
+
|
708
721
|
index = column_names.index(key) || column_names.index(key.to_sym)
|
709
722
|
if index
|
710
723
|
# replace every instance of the array of attributes with our value
|
@@ -718,6 +731,8 @@ class ActiveRecord::Base
|
|
718
731
|
connection.add_column_for_on_duplicate_key_update(key, options)
|
719
732
|
end
|
720
733
|
end
|
734
|
+
|
735
|
+
timestamps
|
721
736
|
end
|
722
737
|
|
723
738
|
# Returns an Array of Hashes for the passed in +column_names+ and +array_of_attributes+.
|
data/test/import_test.rb
CHANGED
@@ -183,6 +183,24 @@ describe "#import" do
|
|
183
183
|
end
|
184
184
|
end
|
185
185
|
|
186
|
+
it "should set ActiveRecord timestamps in valid models if adapter supports setting primary key of imported objects" do
|
187
|
+
if ActiveRecord::Base.support_setting_primary_key_of_imported_objects?
|
188
|
+
Timecop.freeze(Time.at(0)) do
|
189
|
+
Topic.import (invalid_models + valid_models), validate: true
|
190
|
+
end
|
191
|
+
|
192
|
+
assert_nil invalid_models[0].created_at
|
193
|
+
assert_nil invalid_models[0].updated_at
|
194
|
+
assert_nil invalid_models[1].created_at
|
195
|
+
assert_nil invalid_models[1].updated_at
|
196
|
+
|
197
|
+
assert_equal valid_models[0].created_at, Topic.all[0].created_at
|
198
|
+
assert_equal valid_models[0].updated_at, Topic.all[0].updated_at
|
199
|
+
assert_equal valid_models[1].created_at, Topic.all[1].created_at
|
200
|
+
assert_equal valid_models[1].updated_at, Topic.all[1].updated_at
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
186
204
|
it "should import valid data when mixed with invalid data" do
|
187
205
|
assert_difference "Topic.count", +2 do
|
188
206
|
Topic.import columns, valid_values + invalid_values, validate: true
|
@@ -463,12 +481,12 @@ describe "#import" do
|
|
463
481
|
|
464
482
|
context "importing through an association scope" do
|
465
483
|
{ has_many: :chapters, polymorphic: :discounts }.each do |association_type, association|
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
484
|
+
book = FactoryGirl.create :book
|
485
|
+
scope = book.public_send association
|
486
|
+
klass = { chapters: Chapter, discounts: Discount }[association]
|
487
|
+
column = { chapters: :title, discounts: :amount }[association]
|
488
|
+
val1 = { chapters: 'A', discounts: 5 }[association]
|
489
|
+
val2 = { chapters: 'B', discounts: 6 }[association]
|
472
490
|
|
473
491
|
context "for #{association_type}" do
|
474
492
|
it "works importing models" do
|
@@ -114,7 +114,7 @@ ActiveRecord::Schema.define do
|
|
114
114
|
add_index :animals, [:name], unique: true, name: 'uk_animals'
|
115
115
|
|
116
116
|
create_table :widgets, id: false, force: :cascade do |t|
|
117
|
-
t.integer :w_id
|
117
|
+
t.integer :w_id, primary_key: true
|
118
118
|
t.boolean :active, default: false
|
119
119
|
t.text :data
|
120
120
|
t.text :json_data
|
@@ -1,6 +1,11 @@
|
|
1
1
|
class ActiveSupport::TestCase
|
2
2
|
include ActiveRecord::TestFixtures
|
3
|
-
|
3
|
+
|
4
|
+
if ENV['AR_VERSION'].to_f >= 5.0
|
5
|
+
self.use_transactional_tests = true
|
6
|
+
else
|
7
|
+
self.use_transactional_fixtures = true
|
8
|
+
end
|
4
9
|
|
5
10
|
class << self
|
6
11
|
def requires_active_record_version(version_string, &blk)
|
@@ -323,7 +323,7 @@ def should_support_postgresql_upsert_functionality
|
|
323
323
|
context "with no primary key" do
|
324
324
|
it "raises ArgumentError" do
|
325
325
|
error = assert_raises ArgumentError do
|
326
|
-
|
326
|
+
Rule.import Build(3, :rules), on_duplicate_key_update: [:condition_text], validate: false
|
327
327
|
end
|
328
328
|
assert_match(/Expected :conflict_target or :constraint_name to be specified/, error.message)
|
329
329
|
end
|
@@ -102,6 +102,21 @@ def should_support_recursive_import
|
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
105
|
+
describe "with composite primary keys" do
|
106
|
+
it "should import models and set id" do
|
107
|
+
tags = []
|
108
|
+
tags << Tag.new(tag_id: 1, publisher_id: 1, tag: 'Mystery')
|
109
|
+
tags << Tag.new(tag_id: 2, publisher_id: 1, tag: 'Science')
|
110
|
+
|
111
|
+
assert_difference "Tag.count", +2 do
|
112
|
+
Tag.import tags
|
113
|
+
end
|
114
|
+
|
115
|
+
assert_equal 1, tags[0].tag_id
|
116
|
+
assert_equal 2, tags[1].tag_id
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
105
120
|
# These models dont validate associated. So we expect that books and topics get inserted, but not chapters
|
106
121
|
# Putting a transaction around everything wouldn't work, so if you want your chapters to prevent topics from
|
107
122
|
# being created, you would need to have validates_associated in your models and insert with validation
|
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.17.
|
4
|
+
version: 0.17.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zach Dennis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-02-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -38,8 +38,7 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
-
description:
|
42
|
-
for Rails 3 and beyond
|
41
|
+
description: A library for bulk inserting data using ActiveRecord.
|
43
42
|
email:
|
44
43
|
- zach.dennis@gmail.com
|
45
44
|
executables: []
|
@@ -178,7 +177,7 @@ rubyforge_project:
|
|
178
177
|
rubygems_version: 2.6.2
|
179
178
|
signing_key:
|
180
179
|
specification_version: 4
|
181
|
-
summary: Bulk
|
180
|
+
summary: Bulk insert extension for ActiveRecord
|
182
181
|
test_files:
|
183
182
|
- test/adapters/jdbcmysql.rb
|
184
183
|
- test/adapters/jdbcpostgresql.rb
|