mass_insert 0.0.1 → 0.0.2
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.
- data/README.md +52 -1
- data/Rakefile +0 -2
- data/lib/mass_insert/adapters/mysql2_adapter.rb +1 -1
- data/lib/mass_insert/base.rb +42 -19
- data/lib/mass_insert/process_control.rb +29 -5
- data/lib/mass_insert/version.rb +1 -1
- data/spec/active_record_dummy/config/database.yml +1 -1
- data/spec/active_record_models/model_spec.rb +3 -3
- data/spec/mass_insert/base_spec.rb +18 -2
- data/spec/mass_insert/process_control_spec.rb +89 -13
- metadata +4 -4
data/README.md
CHANGED
@@ -41,10 +41,61 @@ The array of values:
|
|
41
41
|
}
|
42
42
|
]
|
43
43
|
|
44
|
-
And call mass_insert method:
|
44
|
+
And call mass_insert method from your model:
|
45
45
|
|
46
46
|
User.mass_insert(values)
|
47
47
|
|
48
|
+
|
49
|
+
## Results
|
50
|
+
|
51
|
+
Sometimes after MassInsert process you need to see some necessary information about the process. MassInsert gem provides a simple way to do it. Only call the next methods from your model after MassInsert execution.
|
52
|
+
|
53
|
+
User.mass_insert_results.records # => 120000
|
54
|
+
|
55
|
+
Some result options are...
|
56
|
+
|
57
|
+
1. `records` : Returns the amount of records that were persisted.
|
58
|
+
2. `time` : Returns the time that took to do all the MassInsert process.
|
59
|
+
3. `build_time` : Returns the time that took to create the query string that was persisted.
|
60
|
+
4. `execute_time` : Returns the time that took to execute the query string that was persisted.
|
61
|
+
|
62
|
+
|
63
|
+
## Options
|
64
|
+
|
65
|
+
MassInsert accepts options hash by second param when you call `mass_insert` from your model. This options allow you to configure the way that the records will be created. Example...
|
66
|
+
|
67
|
+
options = {
|
68
|
+
:primary_key => :user_id,
|
69
|
+
:primary_key_mode => :manual
|
70
|
+
}
|
71
|
+
|
72
|
+
User.mass_insert(values, options)
|
73
|
+
|
74
|
+
OR directly
|
75
|
+
|
76
|
+
User.mass_insert(values, :primary_key => :user_id, :primary_key_mode => :manual)
|
77
|
+
|
78
|
+
Some options that you can include are...
|
79
|
+
|
80
|
+
1. `:table_name` : Default value is the table name to your model. This options rarely needs to change but you can do it if you pass a string with the table name. Example...
|
81
|
+
|
82
|
+
options = {:table_name => "users"}
|
83
|
+
|
84
|
+
2. `:primary_key` : Default value is `:id`. You can change the name of primary key column send it a symbol with the column name.
|
85
|
+
|
86
|
+
options = {:primary_key => :post_id}
|
87
|
+
|
88
|
+
3. `:primary_key_mode` : Default value is `:auto`. When is `:auto` MassInsert knows that the database will generate the value of the primary key column automatically. If you pass `:manual` as primary key mode you need to create your value hashes with the key and value of the primary key column.
|
89
|
+
|
90
|
+
options = {:primary_key_mode => :manual}
|
91
|
+
|
92
|
+
|
93
|
+
## Advantages
|
94
|
+
|
95
|
+
Faster. It's depending of the computer but these are some results...
|
96
|
+
|
97
|
+
* PostgreSQL - Saving 10,000 records in 0.84s
|
98
|
+
|
48
99
|
## Attention
|
49
100
|
|
50
101
|
Since this is a single database insertion your model validation will be ignored, then if you use this gem you need to be sure that information is OK to be persisted.
|
data/Rakefile
CHANGED
@@ -4,8 +4,6 @@ namespace :spec do
|
|
4
4
|
desc "Prepares the environment to use the gem"
|
5
5
|
task :prepare do
|
6
6
|
system("
|
7
|
-
bundle install
|
8
|
-
cd spec/active_record_dummy/
|
9
7
|
bundle install
|
10
8
|
rake db:drop db:create db:migrate RAILS_ENV=mysql2
|
11
9
|
rake db:drop db:create db:migrate RAILS_ENV=postgresql
|
@@ -5,7 +5,7 @@ module MassInsert
|
|
5
5
|
# This method is overwrite because the timestamp format to this
|
6
6
|
# database engine does not need precision in nanoseconds.
|
7
7
|
def timestamp_format
|
8
|
-
"%Y-%m-%d %H:%M:%S"
|
8
|
+
@timestamp_format ||= "%Y-%m-%d %H:%M:%S"
|
9
9
|
end
|
10
10
|
|
11
11
|
# This functions calls the necessary functions to create a complete
|
data/lib/mass_insert/base.rb
CHANGED
@@ -5,8 +5,8 @@ module MassInsert
|
|
5
5
|
#
|
6
6
|
# User.mass_insert(values)
|
7
7
|
#
|
8
|
-
# The values should be an array with the object values
|
9
|
-
#
|
8
|
+
# The values should be an array with the object values in a hash.
|
9
|
+
# Example...
|
10
10
|
#
|
11
11
|
# values = [
|
12
12
|
# {:name => "user name", :email => "user email"},
|
@@ -14,30 +14,53 @@ module MassInsert
|
|
14
14
|
# {:name => "user name", :email => "user email"}
|
15
15
|
# ]
|
16
16
|
#
|
17
|
+
# When a class that inherit from ActiveRecord::Base calls this method
|
18
|
+
# is going to extend the methods in ClassMethods module. This module
|
19
|
+
# contains some methods that provides some necessary functionality.
|
20
|
+
#
|
21
|
+
# After extends the class with methods in ClassMethods module. The
|
22
|
+
# options that were passed by params are sanitized in the method
|
23
|
+
# called mass_insert_options_sanitized to be passed by params to
|
24
|
+
# do ProcessControl instance. The values are passed too.
|
17
25
|
def mass_insert values, args = {}
|
26
|
+
class_eval do
|
27
|
+
extend ClassMethods
|
28
|
+
end
|
29
|
+
|
18
30
|
options = mass_insert_options(args)
|
19
|
-
|
20
|
-
|
31
|
+
@mass_insert_process = MassInsert::ProcessControl.new(values, options)
|
32
|
+
@mass_insert_process.start
|
21
33
|
end
|
22
34
|
|
23
|
-
private
|
24
35
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
36
|
+
module ClassMethods
|
37
|
+
# Returns an OpenStruc instance where is possible to see the
|
38
|
+
# results of MassInsert process. This method calls results method
|
39
|
+
# in ProcessControl class. Returns nil if there is not a instance
|
40
|
+
# variable with the MassInset process.
|
41
|
+
def mass_insert_results
|
42
|
+
@mass_insert_process.results if @mass_insert_process
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
30
46
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
47
|
+
def mass_insert_options args = {}
|
48
|
+
# prepare default options that come in the class that invokes the
|
49
|
+
# mass_insert function.
|
50
|
+
args[:class_name] ||= self
|
51
|
+
args[:table_name] ||= self.table_name
|
36
52
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
53
|
+
# prepare attributes options that were configured by the user and
|
54
|
+
# if the options weren't passed, they would be initialized with the
|
55
|
+
# default values.
|
56
|
+
args[:primary_key] ||= :id
|
57
|
+
args[:primary_key_mode] ||= :auto
|
41
58
|
|
59
|
+
# Returns the arguments but sanitized and ready to be used. This
|
60
|
+
# args will be passed by params to ProcessControl class.
|
61
|
+
args
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
42
65
|
end
|
43
66
|
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'ostruct'
|
3
|
+
|
1
4
|
module MassInsert
|
2
5
|
class ProcessControl
|
3
6
|
|
@@ -9,15 +12,36 @@ module MassInsert
|
|
9
12
|
end
|
10
13
|
|
11
14
|
# This method is responsible to call all the necessary process to
|
12
|
-
# complete the mass insertion process
|
13
|
-
|
14
|
-
|
15
|
+
# complete the mass insertion process and save the time each method
|
16
|
+
# takes being executed.
|
17
|
+
def start
|
18
|
+
# Calls the method that build the query.
|
19
|
+
@build_time = Benchmark.measure{ build_query }
|
20
|
+
# Calls the method that execute the query.
|
21
|
+
@execute_time = Benchmark.measure{ execute_query }
|
15
22
|
end
|
16
23
|
|
17
24
|
# Returns the correct query string according to database adapter
|
18
25
|
# previosly configured usually in database.yml in Rails project.
|
19
|
-
def
|
20
|
-
QueryBuilder.new(values, options).build
|
26
|
+
def build_query
|
27
|
+
@query = QueryBuilder.new(values, options).build
|
28
|
+
end
|
29
|
+
|
30
|
+
# This method does a QueryExecution instance where the query will be
|
31
|
+
# execute. The query string is the instance variable @query.
|
32
|
+
def execute_query
|
33
|
+
QueryExecution.new(@query).execute if @query
|
34
|
+
end
|
35
|
+
|
36
|
+
# Provides an OpenStruc instance to see the process results. This
|
37
|
+
# method is usually called from mass_insert_results in Base module.
|
38
|
+
def results
|
39
|
+
result = OpenStruct.new
|
40
|
+
result.time = @build_time.total + @execute_time.total
|
41
|
+
result.records = values.count
|
42
|
+
result.build_time = @build_time.total
|
43
|
+
result.execute_time = @execute_time.total
|
44
|
+
result
|
21
45
|
end
|
22
46
|
|
23
47
|
end
|
data/lib/mass_insert/version.rb
CHANGED
@@ -23,10 +23,10 @@ describe "Model" do
|
|
23
23
|
User.count.should eq(5)
|
24
24
|
end
|
25
25
|
|
26
|
-
it "should save if values cointains
|
27
|
-
|
26
|
+
it "should save if values cointains 1200 records" do
|
27
|
+
1200.times{ @values << @value_hash }
|
28
28
|
User.mass_insert(@values, @options)
|
29
|
-
User.count.should eq(
|
29
|
+
User.count.should eq(1200)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
@@ -6,7 +6,7 @@ describe MassInsert::Base do
|
|
6
6
|
describe "class methods" do
|
7
7
|
describe ".mass_insert" do
|
8
8
|
before :each do
|
9
|
-
MassInsert::ProcessControl.any_instance.stub(:
|
9
|
+
MassInsert::ProcessControl.any_instance.stub(:start => true)
|
10
10
|
end
|
11
11
|
|
12
12
|
it "should respond to mass_insert class method" do
|
@@ -30,7 +30,7 @@ describe MassInsert::Base do
|
|
30
30
|
|
31
31
|
it "should call execute ProcessControl method" do
|
32
32
|
process = MassInsert::ProcessControl.any_instance
|
33
|
-
process.should_receive(:
|
33
|
+
process.should_receive(:start).exactly(1).times
|
34
34
|
Test.mass_insert([], {})
|
35
35
|
end
|
36
36
|
|
@@ -41,6 +41,22 @@ describe MassInsert::Base do
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
+
describe ".mass_insert_results" do
|
45
|
+
it "should respond to mass_insert_results class method" do
|
46
|
+
Test.respond_to?(:mass_insert_results).should be_true
|
47
|
+
end
|
48
|
+
|
49
|
+
context "when mass_insert_process instance variable exists" do
|
50
|
+
it "should call results method in ProcessControl class" do
|
51
|
+
process = MassInsert::ProcessControl
|
52
|
+
process.any_instance.stub(:results).and_return(true)
|
53
|
+
process.any_instance.should_receive(:results).exactly(1).times
|
54
|
+
Test.mass_insert([], {})
|
55
|
+
Test.mass_insert_results
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
44
60
|
describe ".mass_insert_options" do
|
45
61
|
describe "class_name" do
|
46
62
|
it "returns class name that call if that option doesn't exist" do
|
@@ -27,30 +27,106 @@ describe MassInsert::ProcessControl do
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
describe "#
|
31
|
-
|
32
|
-
subject.
|
30
|
+
describe "#start" do
|
31
|
+
before :each do
|
32
|
+
subject.stub(:build_query).and_return(true)
|
33
|
+
subject.stub(:execute_query).and_return(true)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should respond to start method" do
|
37
|
+
subject.respond_to?(:start).should be_true
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should call build_query" do
|
41
|
+
subject.should_receive(:build_query).exactly(1).times
|
42
|
+
subject.start
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should define instance variable @build_time" do
|
46
|
+
subject.start
|
47
|
+
subject.instance_variables.include?(:@build_time).should be_true
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should call execute_query" do
|
51
|
+
subject.should_receive(:execute_query).exactly(1).times
|
52
|
+
subject.start
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should define instance variable @execute_time" do
|
56
|
+
subject.start
|
57
|
+
subject.instance_variables.include?(:@execute_time).should be_true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "#execute_query" do
|
62
|
+
it "should respond to execute_query method" do
|
63
|
+
subject.respond_to?(:execute_query).should be_true
|
64
|
+
end
|
65
|
+
|
66
|
+
context "when query instance variable exists" do
|
67
|
+
it "should instance and call QueryExecution class" do
|
68
|
+
subject.instance_variable_set(:@query, "query")
|
69
|
+
execution = MassInsert::QueryExecution.any_instance
|
70
|
+
execution.stub(:execute).and_return("executed")
|
71
|
+
execution.should_receive(:execute).exactly(1).times
|
72
|
+
subject.execute_query
|
73
|
+
end
|
33
74
|
end
|
34
75
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
76
|
+
context "when query instance variable does not exists" do
|
77
|
+
it "should instance and call QueryExecution class" do
|
78
|
+
subject.instance_variable_set(:@query, nil)
|
79
|
+
execution = MassInsert::QueryExecution.any_instance
|
80
|
+
execution.stub(:execute).and_return("executed")
|
81
|
+
execution.should_not_receive(:execute)
|
82
|
+
subject.execute_query
|
83
|
+
end
|
41
84
|
end
|
42
85
|
end
|
43
86
|
|
44
|
-
describe "#
|
45
|
-
it "should respond to
|
46
|
-
subject.respond_to?(:
|
87
|
+
describe "#build_query" do
|
88
|
+
it "should respond to build_query method" do
|
89
|
+
subject.respond_to?(:build_query).should be_true
|
47
90
|
end
|
48
91
|
|
49
92
|
it "should instance and call QueryBuilder class" do
|
50
93
|
builder = MassInsert::QueryBuilder.any_instance
|
51
94
|
builder.stub(:build).and_return("query")
|
52
|
-
subject.
|
95
|
+
subject.build_query.should eq("query")
|
53
96
|
end
|
97
|
+
|
98
|
+
it "should set query instance variable" do
|
99
|
+
builder = MassInsert::QueryBuilder.any_instance
|
100
|
+
builder.stub(:build).and_return("query")
|
101
|
+
subject.build_query
|
102
|
+
subject.instance_variables.include?(:@query).should be_true
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "#results" do
|
108
|
+
before :each do
|
109
|
+
subject.stub(:values).and_return([{}, {}])
|
110
|
+
a = Benchmark.measure{}
|
111
|
+
a.stub(:total).and_return(10.0)
|
112
|
+
subject.instance_variable_set(:@build_time, a)
|
113
|
+
subject.instance_variable_set(:@execute_time, a)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should return total time in results" do
|
117
|
+
subject.results.time.should eql(20.0)
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should return records persisted in results" do
|
121
|
+
subject.results.records.should eql(2)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should return build time in results" do
|
125
|
+
subject.results.build_time.should eql(10.0)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should return execute time in results" do
|
129
|
+
subject.results.execute_time.should eql(10.0)
|
54
130
|
end
|
55
131
|
end
|
56
132
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mass_insert
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-04-
|
12
|
+
date: 2013-04-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -162,7 +162,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
162
162
|
version: '0'
|
163
163
|
segments:
|
164
164
|
- 0
|
165
|
-
hash: -
|
165
|
+
hash: -391143887748903532
|
166
166
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
167
167
|
none: false
|
168
168
|
requirements:
|
@@ -171,7 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
171
171
|
version: '0'
|
172
172
|
segments:
|
173
173
|
- 0
|
174
|
-
hash: -
|
174
|
+
hash: -391143887748903532
|
175
175
|
requirements: []
|
176
176
|
rubyforge_project:
|
177
177
|
rubygems_version: 1.8.25
|