active_record_bulk_insert 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
+
@@ -0,0 +1,9 @@
1
+ class SampleRecordMigration < ActiveRecord::Migration
2
+ def change
3
+ create_table :sample_records do |t|
4
+ t.text "name"
5
+ t.integer "age"
6
+ end
7
+ end
8
+ end
9
+
@@ -0,0 +1,2 @@
1
+ class SampleRecord < ActiveRecord::Base
2
+ end
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