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.
@@ -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