active_record-pool 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/lib/active_record-pool.rb +5 -0
- data/lib/active_record/base_extension.rb +7 -0
- data/lib/active_record/pool.rb +104 -0
- data/lib/active_record/pool/version.rb +5 -0
- data/spec/lib/active_record/pool/version_spec.rb +7 -0
- data/spec/lib/active_record/pool_spec.rb +59 -0
- data/spec/spec_helper.rb +27 -0
- metadata +168 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1b121f2e0e34ac06dbb412139c8f27ed201bae07
|
4
|
+
data.tar.gz: 999918bde34952536d00121b133c77bfad2a705d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 984ccc454a7187c02852bc8d2aec247dc62ca52334b6fe9f22b7c95457268da834a3c23dc76c8465d898952781f454758c38edc48cfa5660ec4503faa9201809
|
7
|
+
data.tar.gz: c17a8f1f8dda3a4ff0deede9382add61171a68e1428085168fd4742d3bd3b09409e9fb82ea9154f1a828bc824d0de33195ff5b3e82e0f8353914690e196314de
|
@@ -0,0 +1,7 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class Base
|
3
|
+
def self.pool(columns:, query: self, table: self.table_name, size: ActiveRecord::Pool::DEFAULT_SIZE, serializer: ActiveRecord::Pool::DEFAULT_SERIALIZER, &iteration)
|
4
|
+
ActiveRecord::Pool.new(columns: columns, query: query, table: table, size: size, serializer: serializer, model: self, &iteration)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
require_relative "base_extension"
|
3
|
+
|
4
|
+
class Pool
|
5
|
+
require_relative "pool/version"
|
6
|
+
|
7
|
+
DEFAULT_SIZE = 24
|
8
|
+
DEFAULT_SERIALIZER = ::JSON
|
9
|
+
EMPTY_HASH = {}
|
10
|
+
|
11
|
+
# `query` is either an ActiveRecord query object or arel
|
12
|
+
# `columns` is a list of columns you want to have during the transaction
|
13
|
+
# `table` is the table you want to talk to
|
14
|
+
# `size` is the maximum number of running iterations in the pool, default: 24
|
15
|
+
# `serializer` is the #dump duck for Array & Hash values, default: JSON
|
16
|
+
# `model` is an ActiveRecord model
|
17
|
+
# `transaction` is the process you want to run against your database
|
18
|
+
def initialize(query:, columns:, table:, size:, serializer:, model:, &transaction)
|
19
|
+
@query = query
|
20
|
+
@serializer = serializer
|
21
|
+
@table = Arel::Table.new(table)
|
22
|
+
qutex = Mutex.new
|
23
|
+
|
24
|
+
queue = case
|
25
|
+
when activerecord?
|
26
|
+
@query.pluck(*@columns)
|
27
|
+
when arel?
|
28
|
+
ActiveRecord::Base.connection.execute(@query.to_sql).map(&:values)
|
29
|
+
when tuple?
|
30
|
+
@query.map { |result| result.slice(*columns).values }
|
31
|
+
when twodimensional?
|
32
|
+
@query
|
33
|
+
else
|
34
|
+
raise ArgumentError, 'query wasn\'t recognizable, please use some that looks like a: ActiveRecord::Base, Arel::SelectManager, Array[Hash], Array[Array]'
|
35
|
+
end
|
36
|
+
|
37
|
+
puts "Migrating #{queue.count} #{table} records"
|
38
|
+
|
39
|
+
# Spin up a number of threads based on the `maximum` given
|
40
|
+
1.upto(size).map do
|
41
|
+
Thread.new do
|
42
|
+
loop do
|
43
|
+
# Try to get a new queue item
|
44
|
+
item = qutex.synchronize { queue.shift }
|
45
|
+
|
46
|
+
if item.nil?
|
47
|
+
# There is no more work
|
48
|
+
break
|
49
|
+
else
|
50
|
+
# Wait for a free connection
|
51
|
+
model.connection_pool.with_connection do
|
52
|
+
model.transaction do
|
53
|
+
# Execute each statement coming back
|
54
|
+
Array[instance_exec(*item, &transaction)].each do |instruction|
|
55
|
+
next if instruction.nil?
|
56
|
+
model.connection.execute(instruction.to_sql)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end.map(&:join)
|
64
|
+
end
|
65
|
+
|
66
|
+
private def activerecord?
|
67
|
+
@query.kind_of?(ActiveRecord::Base) || @query.kind_of?(ActiveRecord::Relation) || @query < ActiveRecord::Base
|
68
|
+
end
|
69
|
+
|
70
|
+
private def arel?
|
71
|
+
@query.kind_of?(Arel::SelectManager)
|
72
|
+
end
|
73
|
+
|
74
|
+
private def tuple?
|
75
|
+
@query.kind_of?(Array) && @query.first.kind_of?(Hash)
|
76
|
+
end
|
77
|
+
|
78
|
+
private def twodimensional?
|
79
|
+
@query.kind_of?(Array) && @query.first.kind_of?(Array)
|
80
|
+
end
|
81
|
+
|
82
|
+
private def update(id, data)
|
83
|
+
Arel::UpdateManager.new.table(@table).where(@table[:id].eq(id)).set(serialize(data))
|
84
|
+
end
|
85
|
+
|
86
|
+
private def insert(data)
|
87
|
+
Arel::InsertManager.new.tap { |m| m.insert(serialize(data)) }
|
88
|
+
end
|
89
|
+
|
90
|
+
private def delete(id)
|
91
|
+
Arel::DeleteManager.new.from(@table).where(@table[:id].eq(id))
|
92
|
+
end
|
93
|
+
|
94
|
+
private def serialize(data)
|
95
|
+
data.inject(EMPTY_HASH) do |state, (key, value)|
|
96
|
+
if value.is_a?(Array) || value.is_a?(Hash)
|
97
|
+
state.merge(@table[key] => @serializer.dump(value))
|
98
|
+
else
|
99
|
+
state.merge(@table[key] => value)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe ActiveRecord::Pool do
|
4
|
+
with_model :Activity do
|
5
|
+
table do |schema|
|
6
|
+
schema.string :kind
|
7
|
+
schema.string :category
|
8
|
+
end
|
9
|
+
end
|
10
|
+
let(:model) { Activity }
|
11
|
+
|
12
|
+
before do
|
13
|
+
allow(Thread).to receive(:new).and_yield.and_return(instance_double("Thread", join: true))
|
14
|
+
75.times do
|
15
|
+
Activity.create(kind: "like")
|
16
|
+
end
|
17
|
+
25.times do
|
18
|
+
Activity.create(kind: "dislike")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe ".pool" do
|
23
|
+
context 'with an delete' do
|
24
|
+
let(:pool) do
|
25
|
+
model.pool(columns: [:id, :kind]) do |id, kind|
|
26
|
+
if kind == "like" then delete(id) end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "deletes 75 likes" do
|
31
|
+
expect { pool }.to change(Activity.where(kind: "like"), :count).from(75).to(0)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'with an insert' do
|
36
|
+
let(:pool) do
|
37
|
+
model.pool(columns: [:id, :kind]) do |id, kind|
|
38
|
+
if kind == "like" then insert(kind: "response", category: "like") end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "creates 75 responses likes" do
|
43
|
+
expect { pool }.to change(Activity.where(kind: "response", category: "like"), :count).from(0).to(75)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'with an update' do
|
48
|
+
let(:pool) do
|
49
|
+
model.pool(columns: [:id, :kind]) do |id, kind|
|
50
|
+
if kind == "like" then update(id, kind: "dislike") end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it "updates 75 likes to dislikes" do
|
55
|
+
expect { pool }.to change(Activity.where(kind: "dislike"), :count).from(25).to(100)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "pry"
|
2
|
+
require "rspec"
|
3
|
+
require "with_model"
|
4
|
+
require "active_record-pool"
|
5
|
+
|
6
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
7
|
+
|
8
|
+
RSpec.configure do |let|
|
9
|
+
|
10
|
+
# Exit the spec after the first failure
|
11
|
+
let.fail_fast = true
|
12
|
+
|
13
|
+
# Only run a specific file, using the ENV variable
|
14
|
+
# Example: FILE=spec/write/version_spec.rb bundle exec rake spec
|
15
|
+
let.pattern = ENV["FILE"]
|
16
|
+
|
17
|
+
# Show the slowest examples in the suite
|
18
|
+
let.profile_examples = true
|
19
|
+
|
20
|
+
# Colorize the output
|
21
|
+
let.color = true
|
22
|
+
|
23
|
+
# Output as a document string
|
24
|
+
let.default_formatter = "doc"
|
25
|
+
|
26
|
+
let.extend WithModel
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_record-pool
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kurtis Rainbolt-Greene
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-06-15 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: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: with_model
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sqlite3
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.1'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.3'
|
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'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '10.1'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '10.1'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.9'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.9'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: pry-doc
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.6'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.6'
|
125
|
+
description: active_record-pool is an extension to active_record that gives you an
|
126
|
+
interface to write high-speed inserts, updates, & delete in a manageable way.
|
127
|
+
email:
|
128
|
+
- kurtis@laurelandwolf.com
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- lib/active_record-pool.rb
|
134
|
+
- lib/active_record/base_extension.rb
|
135
|
+
- lib/active_record/pool.rb
|
136
|
+
- lib/active_record/pool/version.rb
|
137
|
+
- spec/lib/active_record/pool/version_spec.rb
|
138
|
+
- spec/lib/active_record/pool_spec.rb
|
139
|
+
- spec/spec_helper.rb
|
140
|
+
homepage: http://laurelandwolf.github.io/active_record-pool
|
141
|
+
licenses:
|
142
|
+
- MIT
|
143
|
+
metadata: {}
|
144
|
+
post_install_message:
|
145
|
+
rdoc_options: []
|
146
|
+
require_paths:
|
147
|
+
- lib
|
148
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
154
|
+
requirements:
|
155
|
+
- - ">="
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
requirements: []
|
159
|
+
rubyforge_project:
|
160
|
+
rubygems_version: 2.6.12
|
161
|
+
signing_key:
|
162
|
+
specification_version: 4
|
163
|
+
summary: active_record-pool is an extension to active_record that gives you an interface
|
164
|
+
to write high-speed inserts, updates, & delete in a manageable way.
|
165
|
+
test_files:
|
166
|
+
- spec/lib/active_record/pool/version_spec.rb
|
167
|
+
- spec/lib/active_record/pool_spec.rb
|
168
|
+
- spec/spec_helper.rb
|