switch_point 0.1.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d470827ada389d47ee22e4956e6a45a42203abbb
4
- data.tar.gz: 0b73cf90012e2fb62556830421c220cf01e59b21
3
+ metadata.gz: c52a7c39b406241e2df69b1d0dfae3a6c45d15d3
4
+ data.tar.gz: 606dd78b88ad59873c9d7e187c110163899f1ffb
5
5
  SHA512:
6
- metadata.gz: 62fde9258b6d872a319643c32935cbeda95bf2ce80c56f1ca96a73a2bf519562b51fbdf22feb870be8538418f4c273ec828d1ca2279d4678fb6977a701821cd0
7
- data.tar.gz: 296199d722f3dc08deb552f7258796f2d124346cc1268af7302f209f255cb5f8c2ba27a6fcec4ecfffc44c0a6ed7b193768e2d48c03641c26be33fe00345fe3b
6
+ metadata.gz: 1e9097c7f4e5696728fc57a0f190ccd6a4c65d665f1fbb7cfbc89e67cd77658f288d8a4f16a7360e3e34c103a1fdbb050b132eafc13e077901f1af25f1d8cf75
7
+ data.tar.gz: 02bcf5bc10f0c2c4a3b7ee04f3d33b1a94edcfc0f1b91ef1ff2251963052983c3d24cdb67f8a80b6e73daae802d1448352eb573a364d41559d228752efe89337
@@ -1,2 +1,6 @@
1
+ ## 0.2.0 (2014-05-29)
2
+ - Always send destructive operations to writable connection
3
+ - Fix bug on pooled connections
4
+
1
5
  ## 0.1.0 (2014-05-28)
2
6
  - Initial release
@@ -19,7 +19,7 @@ module SwitchPoint
19
19
  end
20
20
 
21
21
  def readonly!(name)
22
- ProxyRepository.find(name).readonly!
22
+ ProxyRepository.checkout(name).readonly!
23
23
  end
24
24
 
25
25
  def writable_all!
@@ -29,7 +29,7 @@ module SwitchPoint
29
29
  end
30
30
 
31
31
  def writable!(name)
32
- ProxyRepository.find(name).writable!
32
+ ProxyRepository.checkout(name).writable!
33
33
  end
34
34
  end
35
35
  extend ClassMethods
@@ -37,5 +37,12 @@ end
37
37
 
38
38
  ActiveSupport.on_load(:active_record) do
39
39
  require 'switch_point/model'
40
+ require 'switch_point/connection'
40
41
  ActiveRecord::Base.send(:include, SwitchPoint::Model)
42
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
43
+ include SwitchPoint::Connection
44
+ SwitchPoint::Connection::DESTRUCTIVE_METHODS.each do |method_name|
45
+ alias_method_chain method_name, :switch_point
46
+ end
47
+ end
41
48
  end
@@ -14,6 +14,10 @@ module SwitchPoint
14
14
  "#{mode}_#{database_name(name, mode)}".camelize
15
15
  end
16
16
 
17
+ def fetch(name)
18
+ @switch_points.fetch(name)
19
+ end
20
+
17
21
  def keys
18
22
  @switch_points.keys
19
23
  end
@@ -0,0 +1,41 @@
1
+ require 'switch_point/proxy_repository'
2
+
3
+ module SwitchPoint
4
+ module Connection
5
+ # See ActiveRecord::ConnectionAdapters::QueryCache
6
+ DESTRUCTIVE_METHODS = [:insert, :update, :delete]
7
+
8
+ DESTRUCTIVE_METHODS.each do |method_name|
9
+ define_method(:"#{method_name}_with_switch_point") do |*args, &block|
10
+ switch_point = self.pool.instance_variable_get(:@switch_point)
11
+ parent_method = :"#{method_name}_without_switch_point"
12
+ if switch_point
13
+ proxy = ProxyRepository.find(switch_point[:name])
14
+ case switch_point[:mode]
15
+ when :readonly
16
+ Connection.proxy_to_writable(proxy, method_name, *args, &block)
17
+ when :writable
18
+ Connection.purge_readonly_query_cache(proxy)
19
+ send(parent_method, *args, &block)
20
+ else
21
+ raise RuntimeError.new("Unknown mode #{switch_point[:mode]} is given with #{name}")
22
+ end
23
+ else
24
+ send(parent_method, *args, &block)
25
+ end
26
+ end
27
+ end
28
+
29
+ def self.proxy_to_writable(proxy, method_name, *args, &block)
30
+ proxy.with_writable do
31
+ proxy.connection.send(method_name, *args, &block)
32
+ end
33
+ end
34
+
35
+ def self.purge_readonly_query_cache(proxy)
36
+ proxy.with_readonly do
37
+ proxy.connection.clear_query_cache
38
+ end
39
+ end
40
+ end
41
+ end
@@ -5,21 +5,19 @@ module SwitchPoint
5
5
  def self.included(model)
6
6
  model.singleton_class.class_eval do
7
7
  include ClassMethods
8
- prepend ConnectionHook
8
+ alias_method_chain :connection, :switch_point
9
9
  end
10
10
  end
11
11
 
12
- module ConnectionHook
13
- def connection
12
+ module ClassMethods
13
+ def connection_with_switch_point
14
14
  if @switch_point_name
15
15
  switch_point_proxy.connection
16
16
  else
17
- super
17
+ connection_without_switch_point
18
18
  end
19
19
  end
20
- end
21
20
 
22
- module ClassMethods
23
21
  def with_readonly(&block)
24
22
  switch_point_proxy.with_readonly(&block)
25
23
  end
@@ -28,14 +26,19 @@ module SwitchPoint
28
26
  switch_point_proxy.with_writable(&block)
29
27
  end
30
28
 
31
- private
32
-
33
29
  def use_switch_point(name)
30
+ assert_existing_switch_point!(name)
34
31
  @switch_point_name = name
35
32
  end
36
33
 
34
+ private
35
+
36
+ def assert_existing_switch_point!(name)
37
+ SwitchPoint.config.fetch(name)
38
+ end
39
+
37
40
  def switch_point_proxy
38
- @switch_point_proxy ||= ProxyRepository.find(@switch_point_name)
41
+ ProxyRepository.checkout(@switch_point_name)
39
42
  end
40
43
  end
41
44
  end
@@ -1,5 +1,3 @@
1
- require 'switch_point/writable_connection_hook'
2
-
3
1
  module SwitchPoint
4
2
  class Proxy
5
3
  def initialize(name)
@@ -8,9 +6,8 @@ module SwitchPoint
8
6
  model = define_model(SwitchPoint.config.model_name(name, mode))
9
7
  @models[mode] = model
10
8
  model.establish_connection(SwitchPoint.config.database_name(name, mode))
11
- memorize_switch_point_name(name, model.connection)
9
+ memorize_switch_point(name, mode, model.connection)
12
10
  end
13
- @models[:writable].connection.extend(WritableConnectionHook)
14
11
  @mode = :readonly
15
12
  end
16
13
 
@@ -20,8 +17,9 @@ module SwitchPoint
20
17
  model
21
18
  end
22
19
 
23
- def memorize_switch_point_name(name, connection)
24
- connection.instance_variable_set(:@switch_point_name, name)
20
+ def memorize_switch_point(name, mode, connection)
21
+ switch_point = { name: name, mode: mode }
22
+ connection.pool.instance_variable_set(:@switch_point, switch_point)
25
23
  end
26
24
 
27
25
  def readonly!
@@ -5,14 +5,22 @@ module SwitchPoint
5
5
  class ProxyRepository
6
6
  include Singleton
7
7
 
8
+ def self.checkout(name)
9
+ instance.checkout(name)
10
+ end
11
+
8
12
  def self.find(name)
9
13
  instance.find(name)
10
14
  end
11
15
 
12
- def find(name)
16
+ def checkout(name)
13
17
  proxies[name] ||= Proxy.new(name)
14
18
  end
15
19
 
20
+ def find(name)
21
+ proxies.fetch(name)
22
+ end
23
+
16
24
  def proxies
17
25
  @proxies ||= {}
18
26
  end
@@ -1,3 +1,3 @@
1
1
  module SwitchPoint
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -38,10 +38,8 @@ RSpec.configure do |config|
38
38
  end
39
39
 
40
40
  config.after(:each) do
41
- Book.delete_all
42
- Book.with_writable do
43
- Book.delete_all
44
- end
41
+ Book.delete_all # This queries to writable database
42
+ FileUtils.cp('main_writable.sqlite3', 'main_readonly.sqlite3')
45
43
  end
46
44
  end
47
45
 
@@ -1,4 +1,26 @@
1
1
  RSpec.describe SwitchPoint::Model do
2
+ describe '.use_switch_point' do
3
+ after do
4
+ Book.use_switch_point :main
5
+ end
6
+
7
+ it 'changes connection' do
8
+ expect(Book).to connect_to('main_readonly.sqlite3')
9
+ Book.use_switch_point :comment
10
+ expect(Book).to connect_to('comment_readonly.sqlite3')
11
+ end
12
+
13
+ context 'with non-existing switch point name' do
14
+ it 'raises error' do
15
+ expect {
16
+ Class.new(ActiveRecord::Base) do
17
+ use_switch_point :not_found
18
+ end
19
+ }.to raise_error(KeyError)
20
+ end
21
+ end
22
+ end
23
+
2
24
  describe '.connection' do
3
25
  it 'returns readonly connection by default' do
4
26
  expect(Book).to connect_to('main_readonly.sqlite3')
@@ -8,6 +30,21 @@ RSpec.describe SwitchPoint::Model do
8
30
  expect(Note).to connect_to('default.sqlite3')
9
31
  end
10
32
 
33
+ it 'sends destructive queries to writable' do
34
+ Book.create
35
+ Book.with_readonly { expect(Book.count).to eq(0) }
36
+ Book.with_writable { expect(Book.count).to eq(1) }
37
+ end
38
+
39
+ it 'works with newly checked-out connection' do
40
+ Thread.start do
41
+ expect(Book.connection.pool.connections.size).to be > 1 # Assertion
42
+ Book.create
43
+ Book.with_readonly { expect(Book.count).to eq(0) }
44
+ Book.with_writable { expect(Book.count).to eq(1) }
45
+ end.join
46
+ end
47
+
11
48
  context 'without switch_point configuration' do
12
49
  it 'returns default connection' do
13
50
  expect(Note.connection).to equal(ActiveRecord::Base.connection)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: switch_point
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kohei Suzuki
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-28 00:00:00.000000000 Z
11
+ date: 2014-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: appraisal
@@ -115,11 +115,11 @@ files:
115
115
  - gemfiles/rails_4.1.gemfile
116
116
  - lib/switch_point.rb
117
117
  - lib/switch_point/config.rb
118
+ - lib/switch_point/connection.rb
118
119
  - lib/switch_point/model.rb
119
120
  - lib/switch_point/proxy.rb
120
121
  - lib/switch_point/proxy_repository.rb
121
122
  - lib/switch_point/version.rb
122
- - lib/switch_point/writable_connection_hook.rb
123
123
  - spec/models.rb
124
124
  - spec/spec_helper.rb
125
125
  - spec/switch_point/model_spec.rb
@@ -1,17 +0,0 @@
1
- require 'switch_point/proxy_repository'
2
-
3
- module SwitchPoint
4
- # Propagate clear_query_cache in writable connection to readonly connection
5
- module WritableConnectionHook
6
- # See ActiveRecord::ConnectionAdapters::QueryCache
7
- [:insert, :update, :delete].each do |method_name|
8
- define_method(method_name) do |*args, &block|
9
- proxy = ProxyRepository.find(@switch_point_name)
10
- proxy.with_readonly do
11
- proxy.connection.clear_query_cache
12
- end
13
- super(*args, &block)
14
- end
15
- end
16
- end
17
- end