active_record_bulk_insert 1.0.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 +7 -0
- data/README.md +77 -0
- data/lib/base.rb +53 -0
- data/spec/sample_record_spec.rb +89 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/migrations/1_sample_record_migration.rb +9 -0
- data/spec/support/sample_record.rb +2 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 65eefc8f4eff1019d60778a09edd78d794e8eaec
|
4
|
+
data.tar.gz: 758c122f661de71f7402dab334bdbb176f767915
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0b86eabb77daa541c1a5e3c0a43c1c954d1e0d1f9c3f54a3f35b4af27b191318c4aa0bb489ea6ab6fdf14244c750300e56f4a1c337c1924b1181e38db787eed2
|
7
|
+
data.tar.gz: 16c35e55835dc81a7e29d1be0a58b5b2c2ce1067a07337d87af2b09f8c4c94e2610ad4a76b67c88b2caf658fe829b7c90853d6f98e28b9cb4f2c7af7891d659b
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# BULK INSERT
|
2
|
+
|
3
|
+
|
4
|
+
### Quick Example
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
#Gemfile
|
8
|
+
gem "active_record_bulk_insert"
|
9
|
+
```
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
users = [{:username => "foo", :firstname => "Foo", :lastname => "Bar", :age => 31},
|
13
|
+
{:username => "j.doe", :firstname => "John", :lastname => "Doe", :age => 57}]
|
14
|
+
User.bulk_insert(users)
|
15
|
+
User.count # => 2
|
16
|
+
```
|
17
|
+
|
18
|
+
### Insert ActiveRecord Objects
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
user1 = User.new({:username => "foo", :firstname => "Foo", :lastname => "Bar", :age => 31})
|
22
|
+
user2 = User.new({:username => "j.doe", :firstname => "John", :lastname => "Doe", :age => 57})
|
23
|
+
User.bulk_insert([user1, user2])
|
24
|
+
User.count # => 2
|
25
|
+
```
|
26
|
+
|
27
|
+
### Evoke Active Record Validations (after_intialize and validation callbacks)
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class User < ActiveRecord
|
31
|
+
validates :username, :presence => true
|
32
|
+
end
|
33
|
+
users = [{:username => "foo", :firstname => "Foo", :lastname => "Bar", :age => 31},
|
34
|
+
{:username => "j.doe", :firstname => "John", :lastname => "Doe", :age => 57},
|
35
|
+
{:firstname => "John", :lastname => "Doe", :age => 57}]
|
36
|
+
User.bulk_insert(users, :validate => true)
|
37
|
+
# => [{:firstname => "John", :lastname => "Doe", :age => 57}]
|
38
|
+
```
|
39
|
+
*The return value is a list of invalid records*
|
40
|
+
|
41
|
+
### Provide your own primary keys
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
users = [{:id => 200, :username => "foo", :firstname => "Foo", :lastname => "Bar", :age => 31},
|
45
|
+
:id => 201, {:username => "j.doe", :firstname => "John", :lastname => "Doe", :age => 57}]
|
46
|
+
User.bulk_insert(users, :use_provided_primary_key => true)
|
47
|
+
```
|
48
|
+
*note this is only available from ActiveRecord 4.0 as id was protected from mass-assignment in ActiveRecord < 4.0*
|
49
|
+
|
50
|
+
### Bulk insert in batches
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
users = 1000000.times.map do |i|
|
54
|
+
{:username => "foo#{i}", :firstname => "Foo#{i}", :lastname => "Bar#{i}", :age => (30..70).to_a.sample}
|
55
|
+
end
|
56
|
+
User.bulk_insert_in_batches(users, :batch_size => 10000)
|
57
|
+
User.count # => 1000000
|
58
|
+
```
|
59
|
+
|
60
|
+
### Benchmark
|
61
|
+
DB: PostgreSQL 9.1.9
|
62
|
+
OS: Debian GNU/Linux 7 (wheezy)
|
63
|
+
Processor: Intel(R) Core(TM) i7-4960HQ CPU @ 2.60GHz
|
64
|
+
Memory: 6GB
|
65
|
+
Number of Records: 10,000
|
66
|
+
|
67
|
+
```
|
68
|
+
user system total real
|
69
|
+
Create with Active Record 1.970000 6.810000 8.780000 ( 14.822348)
|
70
|
+
AR with validations 0.280000 0.000000 0.280000 ( 0.299018)
|
71
|
+
AR without validations 0.190000 0.000000 0.190000 ( 0.207807)
|
72
|
+
Hash without validations 0.080000 0.000000 0.080000 ( 0.108874)
|
73
|
+
Hash with validations 0.690000 0.000000 0.690000 ( 0.669952)
|
74
|
+
```
|
75
|
+
|
76
|
+
License
|
77
|
+
bulk_insert is released under [MIT license.](http://opensource.org/licenses/MIT)
|
data/lib/base.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
ActiveRecord::Base.class_eval do
|
2
|
+
def self.bulk_insert_in_batches(attrs, options = {})
|
3
|
+
batch_size = options.fetch(:batch_size, 1000)
|
4
|
+
|
5
|
+
attrs.each_slice(batch_size).map do |sliced_attrs|
|
6
|
+
bulk_insert(sliced_attrs, options)
|
7
|
+
end.flatten.compact
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.bulk_insert(attrs, options = {})
|
11
|
+
use_provided_primary_key = options.fetch(:use_provided_primary_key, false)
|
12
|
+
attributes = _resolve_record(attrs.first, use_provided_primary_key).keys.join(", ")
|
13
|
+
|
14
|
+
if options.fetch(:validate, false)
|
15
|
+
attrs, invalid = attrs.partition { |record| _validate(record) }
|
16
|
+
end
|
17
|
+
|
18
|
+
values_sql = attrs.map do |record|
|
19
|
+
"(#{_resolve_record(record, use_provided_primary_key).values.map { |r| sanitize(r) }.join(', ')})"
|
20
|
+
end.join(",")
|
21
|
+
|
22
|
+
sql = <<-SQL
|
23
|
+
INSERT INTO #{quoted_table_name}
|
24
|
+
(#{attributes})
|
25
|
+
VALUES
|
26
|
+
#{values_sql}
|
27
|
+
SQL
|
28
|
+
connection.execute(sql) unless attrs.empty?
|
29
|
+
invalid
|
30
|
+
end
|
31
|
+
|
32
|
+
def self._resolve_record(record, use_provided_primary_key)
|
33
|
+
if record.is_a?(Hash) && use_provided_primary_key
|
34
|
+
record.except(primary_key).except(primary_key.to_sym)
|
35
|
+
elsif record.is_a?(Hash)
|
36
|
+
record
|
37
|
+
elsif record.is_a?(ActiveRecord::Base) && use_provided_primary_key
|
38
|
+
record.attributes
|
39
|
+
elsif record.is_a?(ActiveRecord::Base)
|
40
|
+
record.attributes.except(primary_key).except(primary_key.to_sym)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self._validate(record)
|
45
|
+
if record.is_a?(Hash)
|
46
|
+
new(record).valid?
|
47
|
+
elsif record.is_a?(ActiveRecord::Base)
|
48
|
+
record.valid?
|
49
|
+
else
|
50
|
+
false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SampleRecord do
|
4
|
+
describe "bulk_insert" do
|
5
|
+
it "inserts records into the DB" do
|
6
|
+
ActiveRecord::ConnectionAdapters::SQLite3Adapter.any_instance.should_receive(:execute).with do |params|
|
7
|
+
params.should include("INSERT INTO \"sample_records\"")
|
8
|
+
params.should include("Foo")
|
9
|
+
params.should include("30")
|
10
|
+
end.once
|
11
|
+
SampleRecord.bulk_insert([{:name => "Foo", :age => 30}])
|
12
|
+
end
|
13
|
+
|
14
|
+
it "inserts records into the DB and increases count of records" do
|
15
|
+
records = 5.times.map { |i| SampleRecord.new(:age => i + (30..50).to_a.sample, :name => "Foo#{i}").attributes }
|
16
|
+
expect {SampleRecord.bulk_insert(records)}.to change{SampleRecord.count}.by(records.size)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "inserts multiple records into the DB in a single insert statement" do
|
20
|
+
records = 10.times.map { |i| {:age => 4, :name => "Foo#{i}"} }
|
21
|
+
|
22
|
+
ActiveRecord::ConnectionAdapters::SQLite3Adapter.any_instance.should_receive(:execute).with do |params|
|
23
|
+
matchdata = params.match(/insert into "sample_records"/i)
|
24
|
+
matchdata.to_a.count.should == 1
|
25
|
+
records.each do |record|
|
26
|
+
params.should include(record[:age].to_s)
|
27
|
+
params.should include(record[:name])
|
28
|
+
end
|
29
|
+
end.once
|
30
|
+
|
31
|
+
SampleRecord.bulk_insert(records)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "relies on the DB to provide primary_key if :use_provided_primary_key is false or nil" do
|
35
|
+
records = 10.times.map { |i| SampleRecord.new(:id => 10000 + i, :age => 4, :name => "Foo#{i}") }
|
36
|
+
|
37
|
+
ActiveRecord::ConnectionAdapters::SQLite3Adapter.any_instance.should_receive(:execute).with do |params|
|
38
|
+
records.each do |record|
|
39
|
+
params.should_not include(record.id.to_s)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
SampleRecord.bulk_insert(records)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "uses provided primary_key if :use_provided_primary_key is true" do
|
47
|
+
records = 10.times.map { |i| SampleRecord.new(:id => 10000 + i, :age => 4, :name => "Foo#{i}") }
|
48
|
+
|
49
|
+
SampleRecord.bulk_insert(records, :use_provided_primary_key => true)
|
50
|
+
records.each do |record|
|
51
|
+
SampleRecord.exists?(:id => record.id).should be_true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it "support insertion of ActiveRecord objects" do
|
56
|
+
records = 10.times.map { |i| SampleRecord.new(:age => 4, :name => "Foo#{i}") }
|
57
|
+
|
58
|
+
ActiveRecord::ConnectionAdapters::SQLite3Adapter.any_instance.should_receive(:execute).with do |params|
|
59
|
+
matchdata = params.match(/insert into "sample_records"/i)
|
60
|
+
matchdata.to_a.count.should == 1
|
61
|
+
records.each do |record|
|
62
|
+
params.should include(record.age.to_s)
|
63
|
+
params.should include(record.name)
|
64
|
+
end
|
65
|
+
end.once
|
66
|
+
|
67
|
+
SampleRecord.bulk_insert(records)
|
68
|
+
end
|
69
|
+
|
70
|
+
context "validations" do
|
71
|
+
it "should not persist invalid records if ':validate => true' is specified" do
|
72
|
+
SampleRecord.send(:validates, :name, :presence => true)
|
73
|
+
expect {SampleRecord.bulk_insert([:age => 30], :validate => true)}.to_not change{SampleRecord.count}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "bulk_insert_in_batches" do
|
79
|
+
it "allows you to specify a batch_size" do
|
80
|
+
records = 10.times.map { |i| SampleRecord.new(:age => 4, :name => "Foo#{i}").attributes }
|
81
|
+
|
82
|
+
ActiveRecord::ConnectionAdapters::SQLite3Adapter.any_instance.should_receive(:execute).with do |params|
|
83
|
+
params.should include("INSERT INTO \"sample_records\"")
|
84
|
+
end.exactly(5).times
|
85
|
+
|
86
|
+
SampleRecord.bulk_insert_in_batches(records, :batch_size => 2)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "active_record"
|
2
|
+
require 'base'
|
3
|
+
require "database_cleaner"
|
4
|
+
require "support/sample_record"
|
5
|
+
|
6
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
7
|
+
ActiveRecord::Migration.verbose = false
|
8
|
+
ActiveRecord::Migrator.migrate("spec/support/migrations")
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
12
|
+
config.order = "random"
|
13
|
+
|
14
|
+
config.before(:suite) do
|
15
|
+
DatabaseCleaner.strategy = :transaction
|
16
|
+
DatabaseCleaner.clean_with(:truncation)
|
17
|
+
end
|
18
|
+
|
19
|
+
config.before(:each) do
|
20
|
+
DatabaseCleaner.start
|
21
|
+
end
|
22
|
+
|
23
|
+
config.after(:each) do
|
24
|
+
DatabaseCleaner.clean
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_record_bulk_insert
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Abejide Ayodele
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.0.0
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: database_cleaner
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.0.1
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.0.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.13.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.13.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sqlite3
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.3.7
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.3.7
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pg
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.17.1
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.17.1
|
83
|
+
description: Exposes a bulk insert API to AR subclasses
|
84
|
+
email:
|
85
|
+
- abejideayodele@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- lib/base.rb
|
91
|
+
- spec/sample_record_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
- spec/support/migrations/1_sample_record_migration.rb
|
94
|
+
- spec/support/sample_record.rb
|
95
|
+
- README.md
|
96
|
+
homepage: https://github.com/bjhaid/active_record_bulk_insert
|
97
|
+
licenses:
|
98
|
+
- MIT
|
99
|
+
metadata: {}
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
require_paths:
|
103
|
+
- - lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 2.0.3
|
117
|
+
signing_key:
|
118
|
+
specification_version: 4
|
119
|
+
summary: bulk insert records into the DB
|
120
|
+
test_files:
|
121
|
+
- spec/sample_record_spec.rb
|
122
|
+
- spec/spec_helper.rb
|
123
|
+
- spec/support/migrations/1_sample_record_migration.rb
|
124
|
+
- spec/support/sample_record.rb
|