activerecord_save_many 0.2.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.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +18 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/lib/activerecord_save_many.rb +108 -0
- data/spec/activerecord_save_many_spec.rb +104 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- metadata +95 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Trampoline Systems Ltd
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
= activerecord_save_many
|
2
|
+
|
3
|
+
adds a save_many method to ActiveRecord classes. save_many generates reasonable
|
4
|
+
SQL for inserting or updating many ActiveRecord instances in one go
|
5
|
+
|
6
|
+
== Note on Patches/Pull Requests
|
7
|
+
|
8
|
+
* Fork the project.
|
9
|
+
* Make your feature addition or bug fix.
|
10
|
+
* Add tests for it. This is important so I don't break it in a
|
11
|
+
future version unintentionally.
|
12
|
+
* Commit, do not mess with rakefile, version, or history.
|
13
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
14
|
+
* Send me a pull request. Bonus points for topic branches.
|
15
|
+
|
16
|
+
== Copyright
|
17
|
+
|
18
|
+
Copyright (c) 2010 Trampoline Systems Ltd. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "activerecord_save_many"
|
8
|
+
gem.summary = %Q{efficient bulk inserts and updates for ActiveRecord}
|
9
|
+
gem.description = %Q{adds save_many method to ActiveRecord classes for efficient bulk insert and update operations}
|
10
|
+
gem.email = "craig@trampolinesystems.com"
|
11
|
+
gem.homepage = "http://github.com/trampoline/activerecord_save_many"
|
12
|
+
gem.authors = ["mccraigmccraig"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
gem.add_development_dependency "yard", ">= 0"
|
15
|
+
gem.add_development_dependency "rr", ">= 0.10.5"
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
+
end
|
18
|
+
Jeweler::GemcutterTasks.new
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'spec/rake/spectask'
|
24
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
25
|
+
spec.libs << 'lib' << 'spec'
|
26
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
27
|
+
end
|
28
|
+
|
29
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
30
|
+
spec.libs << 'lib' << 'spec'
|
31
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
32
|
+
spec.rcov = true
|
33
|
+
end
|
34
|
+
|
35
|
+
task :spec => :check_dependencies
|
36
|
+
|
37
|
+
task :default => :spec
|
38
|
+
|
39
|
+
begin
|
40
|
+
require 'yard'
|
41
|
+
YARD::Rake::YardocTask.new
|
42
|
+
rescue LoadError
|
43
|
+
task :yardoc do
|
44
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
45
|
+
end
|
46
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module SaveMany
|
5
|
+
MAX_QUERY_SIZE = 1024 * 1024
|
6
|
+
OPTIONS_KEYS = [:columns, :max_rows, :async, :ignore, :update, :updates].to_set
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_accessor :default_max_rows
|
10
|
+
end
|
11
|
+
self.default_max_rows = 50000
|
12
|
+
|
13
|
+
module Functions
|
14
|
+
def rails_env
|
15
|
+
RAILS_ENV if defined? RAILS_ENV
|
16
|
+
end
|
17
|
+
module_function :rails_env
|
18
|
+
|
19
|
+
def disable_async?
|
20
|
+
# for predictable tests we disable delayed insert during testing
|
21
|
+
rails_env()=="test"
|
22
|
+
end
|
23
|
+
module_function :disable_async?
|
24
|
+
|
25
|
+
def check_options(options)
|
26
|
+
unknown_keys = options.keys.to_set - OPTIONS_KEYS.to_set
|
27
|
+
raise "unknown options: #{unknown_keys.to_a.join(", ")}" if !unknown_keys.empty?
|
28
|
+
end
|
29
|
+
module_function :check_options
|
30
|
+
|
31
|
+
# slice an array into smaller arrays with maximum size max_size
|
32
|
+
def slice_array(max_length, arr)
|
33
|
+
slices = []
|
34
|
+
(0..arr.length-1).step( max_length ){ |i| slices << arr.slice(i,max_length) }
|
35
|
+
slices
|
36
|
+
end
|
37
|
+
module_function :slice_array
|
38
|
+
|
39
|
+
def add_columns(klass, values, options)
|
40
|
+
columns = options[:columns] || klass.columns.map(&:name)
|
41
|
+
|
42
|
+
# add a :type column automatically for STI, if not already present
|
43
|
+
if klass.superclass!=ActiveRecord::Base && !columns.include?(:type)
|
44
|
+
columns = [:type, *columns]
|
45
|
+
values = values.map{|vals| [klass.to_s, *vals]}
|
46
|
+
end
|
47
|
+
|
48
|
+
[columns, values]
|
49
|
+
end
|
50
|
+
module_function :add_columns
|
51
|
+
end
|
52
|
+
|
53
|
+
module ClassMethods
|
54
|
+
def save_many_max_rows=(max_rows)
|
55
|
+
@save_many_max_rows=max_rows
|
56
|
+
end
|
57
|
+
|
58
|
+
def save_many_max_rows
|
59
|
+
@save_many_max_rows || ActiveRecord::SaveMany::default_max_rows
|
60
|
+
end
|
61
|
+
|
62
|
+
def save_many(values, options={})
|
63
|
+
Functions::check_options(options)
|
64
|
+
return if values.nil? || values.empty?
|
65
|
+
|
66
|
+
columns, values = Functions::add_columns(self, values, options)
|
67
|
+
|
68
|
+
# if more than max_rows, execute multiple sql statements
|
69
|
+
max_rows = options[:max_rows] || save_many_max_rows
|
70
|
+
batches = Functions::slice_array(max_rows, values)
|
71
|
+
|
72
|
+
column_list = columns.join(', ')
|
73
|
+
do_updates = options[:update] || options[:updates]
|
74
|
+
updates = options[:updates] || {}
|
75
|
+
|
76
|
+
batches.each do |batch|
|
77
|
+
batch = batch.map do |obj|
|
78
|
+
if obj.is_a? ActiveRecord::Base
|
79
|
+
obj.send( :callback, :before_save )
|
80
|
+
if obj.id
|
81
|
+
obj.send( :callback, :before_update)
|
82
|
+
else
|
83
|
+
obj.send( :callback, :before_create )
|
84
|
+
end
|
85
|
+
raise "#{obj.errors.full_messages.join(', ')}" if !obj.valid?
|
86
|
+
end
|
87
|
+
columns.map{|col| obj[col]}
|
88
|
+
end
|
89
|
+
|
90
|
+
insert_stmt = options[:async] && !disable_async? ? "insert delayed" : "insert"
|
91
|
+
ignore_opt = options[:ignore] ? "ignore" : ""
|
92
|
+
|
93
|
+
sql = "#{insert_stmt} #{ignore_opt} into #{table_name} (#{column_list}) values " +
|
94
|
+
batch.map{|vals| "(" + vals.map{|v| quote_value(v)}.join(", ") +")"}.join(", ") +
|
95
|
+
(" on duplicate key update " + columns.map{|c| updates[c] || " #{c} = values(#{c}) "}.join(", ") if do_updates).to_s
|
96
|
+
|
97
|
+
connection.execute_raw sql
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Base
|
104
|
+
class << self
|
105
|
+
include ActiveRecord::SaveMany::ClassMethods
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
describe SaveMany do
|
5
|
+
def new_anon_class(parent, &proc)
|
6
|
+
klass = Class.new(parent)
|
7
|
+
klass.class_eval(&proc) if proc
|
8
|
+
klass
|
9
|
+
end
|
10
|
+
|
11
|
+
def new_named_class(parent, name="", &proc)
|
12
|
+
new_anon_class(parent) do
|
13
|
+
mc=self.instance_eval{ class << self ; self ; end }
|
14
|
+
mc.send(:define_method, :to_s){name}
|
15
|
+
self.class_eval(&proc) if proc
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# create a new anonymous ActiveRecord::Base descendant class
|
20
|
+
def new_ar_class(name="", &proc)
|
21
|
+
new_named_class(ActiveRecord::Base, name, &proc)
|
22
|
+
end
|
23
|
+
|
24
|
+
describe SaveMany::Functions do
|
25
|
+
describe "disable_async?" do
|
26
|
+
it "should disable async inserts when testing" do
|
27
|
+
mock(SaveMany::Functions).rails_env{"test"}
|
28
|
+
SaveMany::Functions::disable_async?.should == true
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should permit async inserts when not testing" do
|
32
|
+
mock(SaveMany::Functions).rails_env{"production"}
|
33
|
+
SaveMany::Functions::disable_async?.should == false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "check_options" do
|
38
|
+
it "should raise if given unknown options" do
|
39
|
+
lambda do
|
40
|
+
SaveMany::Functions::check_options(:foo=>100)
|
41
|
+
end.should raise_error(RuntimeError)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "slice_array" do
|
46
|
+
it "should slice arrays without losing bits" do
|
47
|
+
SaveMany::Functions::slice_array(2,[]).should ==([])
|
48
|
+
SaveMany::Functions::slice_array(2,[1]).should ==([[1]])
|
49
|
+
SaveMany::Functions::slice_array(2,[1,2]).should ==([[1,2]])
|
50
|
+
SaveMany::Functions::slice_array(2,[1,2,3]).should ==([[1,2],[3]])
|
51
|
+
SaveMany::Functions::slice_array(2,[1,2,3,4]).should ==([[1,2],[3,4]])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "add_columns" do
|
56
|
+
it "should add a type column to an indirect inheritor of ActiveRecord::Base" do
|
57
|
+
klass = new_named_class(new_ar_class("Foo"), "Bar")
|
58
|
+
columns, values = SaveMany::Functions::add_columns(klass, [["foo"]], :columns=>[:foo])
|
59
|
+
columns.should == [:type, :foo]
|
60
|
+
values.should == [["Bar", "foo"]]
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should not add a type column if already present" do
|
64
|
+
klass = new_named_class(new_ar_class("Foo"), "Bar")
|
65
|
+
columns, values = SaveMany::Functions::add_columns(klass, [["foo", "Baz"]], :columns=>[:foo, :type])
|
66
|
+
columns.should == [:foo, :type]
|
67
|
+
values.should == [["foo", "Baz"]]
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should not add a type column to a direct inheritor of ActiveRecord::Base" do
|
71
|
+
klass = new_ar_class("Foo")
|
72
|
+
columns, values = SaveMany::Functions::add_columns(klass, [["foo"]], :columns=>[:foo])
|
73
|
+
columns.should == [:foo]
|
74
|
+
values.should == [["foo"]]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "creates a save_many method on an ActiveRecord class" do
|
80
|
+
klass = new_ar_class()
|
81
|
+
klass.respond_to?(:save_many).should be(true)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should have per-class configurable save_many_max_rows" do
|
85
|
+
k1 = new_ar_class()
|
86
|
+
k2 = new_ar_class()
|
87
|
+
k1.save_many_max_rows=1000
|
88
|
+
k2.save_many_max_rows=2000
|
89
|
+
|
90
|
+
k1.save_many_max_rows.should == 1000
|
91
|
+
k2.save_many_max_rows.should == 2000
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should have a global configurable default_max_rows" do
|
95
|
+
k1 = new_ar_class()
|
96
|
+
k2 = new_ar_class()
|
97
|
+
ActiveRecord::SaveMany::default_max_rows = 100
|
98
|
+
k1.save_many_max_rows.should == 100
|
99
|
+
k2.save_many_max_rows.should == 100
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activerecord_save_many
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- mccraigmccraig
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-17 00:00:00 +00:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.9
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: yard
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rr
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.10.5
|
44
|
+
version:
|
45
|
+
description: adds save_many method to ActiveRecord classes for efficient bulk insert and update operations
|
46
|
+
email: craig@trampolinesystems.com
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- LICENSE
|
53
|
+
- README.rdoc
|
54
|
+
files:
|
55
|
+
- .document
|
56
|
+
- .gitignore
|
57
|
+
- LICENSE
|
58
|
+
- README.rdoc
|
59
|
+
- Rakefile
|
60
|
+
- VERSION
|
61
|
+
- lib/activerecord_save_many.rb
|
62
|
+
- spec/activerecord_save_many_spec.rb
|
63
|
+
- spec/spec.opts
|
64
|
+
- spec/spec_helper.rb
|
65
|
+
has_rdoc: true
|
66
|
+
homepage: http://github.com/trampoline/activerecord_save_many
|
67
|
+
licenses: []
|
68
|
+
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options:
|
71
|
+
- --charset=UTF-8
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
version:
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: "0"
|
85
|
+
version:
|
86
|
+
requirements: []
|
87
|
+
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 1.3.5
|
90
|
+
signing_key:
|
91
|
+
specification_version: 3
|
92
|
+
summary: efficient bulk inserts and updates for ActiveRecord
|
93
|
+
test_files:
|
94
|
+
- spec/activerecord_save_many_spec.rb
|
95
|
+
- spec/spec_helper.rb
|