activerecord-import 0.17.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +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
|