switch_point 0.5.0.pre → 0.9.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 +5 -5
- data/.gitignore +1 -0
- data/.rubocop.yml +56 -0
- data/.rubocop_todo.yml +2 -0
- data/.travis.yml +21 -6
- data/Appraisals +55 -5
- data/CHANGELOG.md +23 -1
- data/Gemfile +6 -0
- data/README.md +36 -3
- data/Rakefile +12 -2
- data/assets/switch_point.svg +1 -1
- data/benchmark/proxy.rb +62 -0
- data/gemfiles/rails_3.2.gemfile +6 -2
- data/gemfiles/rails_4.0.gemfile +6 -2
- data/gemfiles/rails_4.1.gemfile +6 -2
- data/gemfiles/rails_4.2.gemfile +11 -0
- data/gemfiles/rails_5.0.gemfile +11 -0
- data/gemfiles/rails_5.1.gemfile +11 -0
- data/gemfiles/rails_5.2.gemfile +11 -0
- data/gemfiles/rails_6.0.gemfile +11 -0
- data/lib/switch_point.rb +17 -8
- data/lib/switch_point/config.rb +15 -15
- data/lib/switch_point/connection.rb +15 -14
- data/lib/switch_point/error.rb +12 -0
- data/lib/switch_point/model.rb +48 -16
- data/lib/switch_point/proxy.rb +23 -5
- data/lib/switch_point/proxy_repository.rb +2 -0
- data/lib/switch_point/query_cache.rb +22 -0
- data/lib/switch_point/version.rb +3 -1
- data/spec/models.rb +18 -5
- data/spec/spec_helper.rb +13 -3
- data/spec/switch_point/model_spec.rb +91 -18
- data/spec/switch_point/query_cache_spec.rb +78 -0
- data/spec/switch_point_spec.rb +69 -0
- data/switch_point.gemspec +22 -17
- metadata +76 -18
- data/gemfiles/rails_edge.gemfile +0 -7
data/lib/switch_point.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/lazy_load_hooks'
|
2
4
|
require 'switch_point/config'
|
3
5
|
require 'switch_point/version'
|
@@ -13,7 +15,7 @@ module SwitchPoint
|
|
13
15
|
end
|
14
16
|
|
15
17
|
def readonly_all!
|
16
|
-
config.
|
18
|
+
config.each_key do |name|
|
17
19
|
readonly!(name)
|
18
20
|
end
|
19
21
|
end
|
@@ -23,7 +25,7 @@ module SwitchPoint
|
|
23
25
|
end
|
24
26
|
|
25
27
|
def writable_all!
|
26
|
-
config.
|
28
|
+
config.each_key do |name|
|
27
29
|
writable!(name)
|
28
30
|
end
|
29
31
|
end
|
@@ -36,10 +38,18 @@ module SwitchPoint
|
|
36
38
|
with_mode(:readonly, *names, &block)
|
37
39
|
end
|
38
40
|
|
41
|
+
def with_readonly_all(&block)
|
42
|
+
with_readonly(*config.keys, &block)
|
43
|
+
end
|
44
|
+
|
39
45
|
def with_writable(*names, &block)
|
40
46
|
with_mode(:writable, *names, &block)
|
41
47
|
end
|
42
48
|
|
49
|
+
def with_writable_all(&block)
|
50
|
+
with_writable(*config.keys, &block)
|
51
|
+
end
|
52
|
+
|
43
53
|
def with_mode(mode, *names, &block)
|
44
54
|
names.reverse.inject(block) do |func, name|
|
45
55
|
lambda do
|
@@ -52,13 +62,12 @@ module SwitchPoint
|
|
52
62
|
end
|
53
63
|
|
54
64
|
ActiveSupport.on_load(:active_record) do
|
55
|
-
require 'switch_point/model'
|
56
65
|
require 'switch_point/connection'
|
57
|
-
|
66
|
+
require 'switch_point/model'
|
67
|
+
require 'switch_point/query_cache'
|
68
|
+
|
69
|
+
ActiveRecord::Base.include SwitchPoint::Model
|
58
70
|
ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
|
59
|
-
|
60
|
-
SwitchPoint::Connection::DESTRUCTIVE_METHODS.each do |method_name|
|
61
|
-
alias_method_chain method_name, :switch_point
|
62
|
-
end
|
71
|
+
prepend SwitchPoint::Connection
|
63
72
|
end
|
64
73
|
end
|
data/lib/switch_point/config.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SwitchPoint
|
2
4
|
class Config
|
5
|
+
attr_accessor :auto_writable
|
6
|
+
alias_method :auto_writable?, :auto_writable
|
7
|
+
|
3
8
|
def initialize
|
4
9
|
self.auto_writable = false
|
5
10
|
end
|
@@ -9,27 +14,17 @@ module SwitchPoint
|
|
9
14
|
switch_points[name] = config
|
10
15
|
end
|
11
16
|
|
12
|
-
def auto_writable=(val)
|
13
|
-
@auto_writable = val
|
14
|
-
end
|
15
|
-
|
16
|
-
def auto_writable?
|
17
|
-
@auto_writable
|
18
|
-
end
|
19
|
-
|
20
17
|
def switch_points
|
21
18
|
@switch_points ||= {}
|
22
19
|
end
|
23
20
|
|
24
21
|
def database_name(name, mode)
|
25
|
-
|
22
|
+
fetch(name)[mode]
|
26
23
|
end
|
27
24
|
|
28
25
|
def model_name(name, mode)
|
29
|
-
if
|
26
|
+
if fetch(name)[mode]
|
30
27
|
"#{name}_#{mode}".camelize
|
31
|
-
else
|
32
|
-
nil
|
33
28
|
end
|
34
29
|
end
|
35
30
|
|
@@ -41,18 +36,23 @@ module SwitchPoint
|
|
41
36
|
switch_points.keys
|
42
37
|
end
|
43
38
|
|
39
|
+
def each_key(&block)
|
40
|
+
switch_points.each_key(&block)
|
41
|
+
end
|
42
|
+
|
44
43
|
private
|
45
44
|
|
46
45
|
def assert_valid_config!(config)
|
47
|
-
unless config.
|
46
|
+
unless config.key?(:readonly) || config.key?(:writable)
|
48
47
|
raise ArgumentError.new(':readonly or :writable must be specified')
|
49
48
|
end
|
50
|
-
|
49
|
+
|
50
|
+
if config.key?(:readonly)
|
51
51
|
unless config[:readonly].is_a?(Symbol)
|
52
52
|
raise TypeError.new(":readonly's value must be Symbol")
|
53
53
|
end
|
54
54
|
end
|
55
|
-
if config.
|
55
|
+
if config.key?(:writable)
|
56
56
|
unless config[:writable].is_a?(Symbol)
|
57
57
|
raise TypeError.new(":writable's value must be Symbol")
|
58
58
|
end
|
@@ -1,36 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'switch_point/error'
|
1
4
|
require 'switch_point/proxy_repository'
|
2
5
|
|
3
6
|
module SwitchPoint
|
4
7
|
module Connection
|
5
8
|
# See ActiveRecord::ConnectionAdapters::QueryCache
|
6
|
-
DESTRUCTIVE_METHODS = [
|
9
|
+
DESTRUCTIVE_METHODS = %i[insert update delete].freeze
|
7
10
|
|
8
11
|
DESTRUCTIVE_METHODS.each do |method_name|
|
9
|
-
define_method(
|
10
|
-
|
11
|
-
|
12
|
-
|
12
|
+
define_method(method_name) do |*args, &block|
|
13
|
+
if pool.equal?(ActiveRecord::Base.connection_pool)
|
14
|
+
Connection.handle_base_connection(self)
|
15
|
+
super(*args, &block)
|
13
16
|
else
|
17
|
+
parent_method = method(method_name).super_method
|
14
18
|
Connection.handle_generated_connection(self, parent_method, method_name, *args, &block)
|
15
19
|
end
|
16
20
|
end
|
17
21
|
end
|
18
22
|
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.handle_base_connection(conn, parent_method, *args, &block)
|
23
|
+
def self.handle_base_connection(conn)
|
23
24
|
switch_points = conn.pool.spec.config[:switch_points]
|
24
25
|
if switch_points
|
25
26
|
switch_points.each do |switch_point|
|
26
27
|
proxy = ProxyRepository.find(switch_point[:name])
|
27
28
|
if switch_point[:mode] != :writable
|
28
|
-
raise
|
29
|
+
raise Error.new("ActiveRecord::Base's switch_points must be writable, but #{switch_point[:name]} is #{switch_point[:mode]}")
|
29
30
|
end
|
31
|
+
|
30
32
|
purge_readonly_query_cache(proxy)
|
31
33
|
end
|
32
34
|
end
|
33
|
-
conn.send(parent_method, *args, &block)
|
34
35
|
end
|
35
36
|
|
36
37
|
def self.handle_generated_connection(conn, parent_method, method_name, *args, &block)
|
@@ -46,12 +47,12 @@ module SwitchPoint
|
|
46
47
|
end
|
47
48
|
when :writable
|
48
49
|
purge_readonly_query_cache(proxy)
|
49
|
-
|
50
|
+
parent_method.call(*args, &block)
|
50
51
|
else
|
51
|
-
raise
|
52
|
+
raise Error.new("Unknown mode #{switch_point[:mode]} is given with #{name}")
|
52
53
|
end
|
53
54
|
else
|
54
|
-
|
55
|
+
parent_method.call(*args, &block)
|
55
56
|
end
|
56
57
|
end
|
57
58
|
|
data/lib/switch_point/model.rb
CHANGED
@@ -1,28 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'switch_point/error'
|
1
4
|
require 'switch_point/proxy_repository'
|
2
5
|
|
3
6
|
module SwitchPoint
|
4
7
|
module Model
|
5
8
|
def self.included(model)
|
9
|
+
super
|
6
10
|
model.singleton_class.class_eval do
|
7
11
|
include ClassMethods
|
8
|
-
|
12
|
+
prepend MonkeyPatch
|
9
13
|
end
|
10
14
|
end
|
11
15
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
switch_point_proxy.connection
|
16
|
-
else
|
17
|
-
connection_without_switch_point
|
18
|
-
end
|
19
|
-
end
|
16
|
+
def with_readonly(&block)
|
17
|
+
self.class.with_readonly(&block)
|
18
|
+
end
|
20
19
|
|
20
|
+
def with_writable(&block)
|
21
|
+
self.class.with_writable(&block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def transaction_with(*models, &block)
|
25
|
+
self.class.transaction_with(*models, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
module ClassMethods
|
21
29
|
def with_readonly(&block)
|
22
30
|
if switch_point_proxy
|
23
31
|
switch_point_proxy.with_readonly(&block)
|
24
32
|
else
|
25
|
-
|
33
|
+
raise UnconfiguredError.new("#{name} isn't configured to use switch_point")
|
26
34
|
end
|
27
35
|
end
|
28
36
|
|
@@ -30,7 +38,7 @@ module SwitchPoint
|
|
30
38
|
if switch_point_proxy
|
31
39
|
switch_point_proxy.with_writable(&block)
|
32
40
|
else
|
33
|
-
|
41
|
+
raise UnconfiguredError.new("#{name} isn't configured to use switch_point")
|
34
42
|
end
|
35
43
|
end
|
36
44
|
|
@@ -40,7 +48,7 @@ module SwitchPoint
|
|
40
48
|
end
|
41
49
|
|
42
50
|
def switch_point_proxy
|
43
|
-
if @switch_point_name
|
51
|
+
if defined?(@switch_point_name)
|
44
52
|
ProxyRepository.checkout(@switch_point_name)
|
45
53
|
elsif self == ActiveRecord::Base
|
46
54
|
nil
|
@@ -51,11 +59,11 @@ module SwitchPoint
|
|
51
59
|
|
52
60
|
def transaction_with(*models, &block)
|
53
61
|
unless can_transaction_with?(*models)
|
54
|
-
raise
|
62
|
+
raise Error.new("switch_point's model names must be consistent")
|
55
63
|
end
|
56
64
|
|
57
65
|
with_writable do
|
58
|
-
|
66
|
+
transaction(&block)
|
59
67
|
end
|
60
68
|
end
|
61
69
|
|
@@ -72,13 +80,37 @@ module SwitchPoint
|
|
72
80
|
model.instance_variable_get(:@switch_point_name),
|
73
81
|
:writable
|
74
82
|
)
|
75
|
-
else
|
76
|
-
nil
|
77
83
|
end
|
78
84
|
end
|
79
85
|
|
80
86
|
writable_switch_points.uniq.size == 1
|
81
87
|
end
|
82
88
|
end
|
89
|
+
|
90
|
+
module MonkeyPatch
|
91
|
+
def connection
|
92
|
+
if switch_point_proxy
|
93
|
+
switch_point_proxy.connection
|
94
|
+
else
|
95
|
+
super
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def cache(&block)
|
100
|
+
if switch_point_proxy
|
101
|
+
switch_point_proxy.cache(&block)
|
102
|
+
else
|
103
|
+
super
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def uncached(&block)
|
108
|
+
if switch_point_proxy
|
109
|
+
switch_point_proxy.uncached(&block)
|
110
|
+
else
|
111
|
+
super
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
83
115
|
end
|
84
116
|
end
|
data/lib/switch_point/proxy.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'switch_point/error'
|
4
|
+
|
1
5
|
module SwitchPoint
|
2
6
|
class Proxy
|
3
7
|
attr_reader :initial_name
|
4
8
|
|
5
|
-
AVAILABLE_MODES = [
|
9
|
+
AVAILABLE_MODES = %i[writable readonly].freeze
|
6
10
|
DEFAULT_MODE = :readonly
|
7
11
|
|
8
12
|
def initialize(name)
|
@@ -26,7 +30,7 @@ module SwitchPoint
|
|
26
30
|
# Re-use writable connection
|
27
31
|
Proxy.const_get(SwitchPoint.config.model_name(name, :writable))
|
28
32
|
else
|
29
|
-
|
33
|
+
ActiveRecord::Base
|
30
34
|
end
|
31
35
|
end
|
32
36
|
|
@@ -34,12 +38,13 @@ module SwitchPoint
|
|
34
38
|
switch_point = { name: name, mode: mode }
|
35
39
|
if pool.equal?(ActiveRecord::Base.connection_pool)
|
36
40
|
if mode != :writable
|
37
|
-
raise
|
41
|
+
raise Error.new("ActiveRecord::Base's switch_points must be writable, but #{name} is #{mode}")
|
38
42
|
end
|
43
|
+
|
39
44
|
switch_points = pool.spec.config[:switch_points] || []
|
40
45
|
switch_points << switch_point
|
41
46
|
pool.spec.config[:switch_points] = switch_points
|
42
|
-
elsif pool.spec.config.
|
47
|
+
elsif pool.spec.config.key?(:switch_point)
|
43
48
|
# Only :writable is specified
|
44
49
|
else
|
45
50
|
pool.spec.config[:switch_point] = switch_point
|
@@ -95,7 +100,8 @@ module SwitchPoint
|
|
95
100
|
unless AVAILABLE_MODES.include?(new_mode)
|
96
101
|
raise ArgumentError.new("Unknown mode: #{new_mode}")
|
97
102
|
end
|
98
|
-
|
103
|
+
|
104
|
+
saved_mode = thread_local_mode
|
99
105
|
self.thread_local_mode = new_mode
|
100
106
|
block.call
|
101
107
|
ensure
|
@@ -142,5 +148,17 @@ module SwitchPoint
|
|
142
148
|
def connected?
|
143
149
|
model_for_connection.connected?
|
144
150
|
end
|
151
|
+
|
152
|
+
def cache(&block)
|
153
|
+
r = with_readonly { model_for_connection }
|
154
|
+
w = with_writable { model_for_connection }
|
155
|
+
r.cache { w.cache(&block) }
|
156
|
+
end
|
157
|
+
|
158
|
+
def uncached(&block)
|
159
|
+
r = with_readonly { model_for_connection }
|
160
|
+
w = with_writable { model_for_connection }
|
161
|
+
r.uncached { w.uncached(&block) }
|
162
|
+
end
|
145
163
|
end
|
146
164
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SwitchPoint
|
4
|
+
class QueryCache
|
5
|
+
def initialize(app, names = nil)
|
6
|
+
@app = app
|
7
|
+
@names = names
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
names.reverse.inject(lambda { @app.call(env) }) do |func, name|
|
12
|
+
lambda { ProxyRepository.checkout(name).cache(&func) }
|
13
|
+
end.call
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def names
|
19
|
+
@names ||= SwitchPoint.config.keys
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|