switch_point 0.5.0.pre → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 5.1.0"
6
+
7
+ platforms :ruby do
8
+ gem "sqlite3"
9
+ end
10
+
11
+ gemspec path: "../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 5.2.0"
6
+
7
+ platforms :ruby do
8
+ gem "sqlite3"
9
+ end
10
+
11
+ gemspec path: "../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 6.0.0"
6
+
7
+ platforms :ruby do
8
+ gem "sqlite3"
9
+ end
10
+
11
+ gemspec path: "../"
@@ -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.keys.each do |name|
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.keys.each do |name|
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
- ActiveRecord::Base.send(:include, SwitchPoint::Model)
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
- include SwitchPoint::Connection
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
@@ -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
- switch_points[name][mode]
22
+ fetch(name)[mode]
26
23
  end
27
24
 
28
25
  def model_name(name, mode)
29
- if switch_points[name][mode]
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.has_key?(:readonly) || config.has_key?(:writable)
46
+ unless config.key?(:readonly) || config.key?(:writable)
48
47
  raise ArgumentError.new(':readonly or :writable must be specified')
49
48
  end
50
- if config.has_key?(:readonly)
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.has_key?(:writable)
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 = [:insert, :update, :delete]
9
+ DESTRUCTIVE_METHODS = %i[insert update delete].freeze
7
10
 
8
11
  DESTRUCTIVE_METHODS.each do |method_name|
9
- define_method(:"#{method_name}_with_switch_point") do |*args, &block|
10
- parent_method = :"#{method_name}_without_switch_point"
11
- if self.pool.equal?(ActiveRecord::Base.connection_pool)
12
- Connection.handle_base_connection(self, parent_method, *args, &block)
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
- class ReadonlyError < StandardError
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 RuntimeError.new("ActiveRecord::Base's switch_points must be writable, but #{switch_point[:name]} is #{switch_point[:mode]}")
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
- conn.send(parent_method, *args, &block)
50
+ parent_method.call(*args, &block)
50
51
  else
51
- raise RuntimeError.new("Unknown mode #{switch_point[:mode]} is given with #{name}")
52
+ raise Error.new("Unknown mode #{switch_point[:mode]} is given with #{name}")
52
53
  end
53
54
  else
54
- conn.send(parent_method, *args, &block)
55
+ parent_method.call(*args, &block)
55
56
  end
56
57
  end
57
58
 
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwitchPoint
4
+ class Error < StandardError
5
+ end
6
+
7
+ class ReadonlyError < Error
8
+ end
9
+
10
+ class UnconfiguredError < Error
11
+ end
12
+ end
@@ -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
- alias_method_chain :connection, :switch_point
12
+ prepend MonkeyPatch
9
13
  end
10
14
  end
11
15
 
12
- module ClassMethods
13
- def connection_with_switch_point
14
- if switch_point_proxy
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
- block.call
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
- block.call
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 RuntimeError.new("switch_point's model names must be consistent")
62
+ raise Error.new("switch_point's model names must be consistent")
55
63
  end
56
64
 
57
65
  with_writable do
58
- self.transaction(&block)
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
@@ -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 = [:writable, :readonly]
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
- Class.new(ActiveRecord::Base)
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 RuntimeError.new("ActiveRecord::Base's switch_points must be writable, but #{name} is #{mode}")
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.has_key?(:switch_point)
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
- saved_mode = self.thread_local_mode
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'singleton'
2
4
  require 'switch_point/proxy'
3
5
 
@@ -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