activerecord_autoreplica 1.2.0 → 1.3.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: 8ed3fdce7e191b78e8bc24c13cc926cb9b625052
4
- data.tar.gz: 02bdd2ae70931f1a7376a9264bf6a5ac6442275e
3
+ metadata.gz: 0dec3a7a02bfdd20f0428bdb405016f3b16d7c12
4
+ data.tar.gz: c84f381a3f3c1d01f2066aec82597333a7a8d2c8
5
5
  SHA512:
6
- metadata.gz: fa680e51be925cbe09927aa323e44be88fd26cf82b799474761bbae59a135a2123e1f459e9e350f9949eb58462338e7dc433558aaa9156463d8881fe141f82cf
7
- data.tar.gz: 7db0c18d68ed48fdbf428d56bff0f49a1ec5137ef8f5c10e695eef9d41ec3e860a52ffff11256eb2525f98c7b004ecf1b4e465d8fa711e1d1dde932252250f5f
6
+ metadata.gz: 9d0de5551b0801a3dd9149111e2610715f27a12619f56fdbb29e3108591c06678e326bb2e7e14836ccc89e6f9ab4fc7eef212d52074bba39b4a37f332e8c852b
7
+ data.tar.gz: e4efd1d3811ffd76b38acb6707b7dbe57babbcad919da69d49e67746236fc15c58219c5ca2bf0b41745c9501f5326a4c925613f55c636a337f4619f3e8a2c72d
data/README.md CHANGED
@@ -17,7 +17,9 @@ The only dependency is ActiveRecord itself.
17
17
 
18
18
  ### Usage
19
19
 
20
- The API consists of _one_ method. Pass it a complete ActiveRecord connection specificaton hash, and
20
+ There are two options.
21
+
22
+ The first is to pass a complete ActiveRecord connection specificaton hash, and
21
23
  everything within the block is going to use the read slave connections when performing standard
22
24
  ActiveRecord `SELECT` queries (not the hand-written ones).
23
25
 
@@ -27,11 +29,21 @@ ActiveRecord `SELECT` queries (not the hand-written ones).
27
29
  end
28
30
 
29
31
  Connection strings (URLs) are also supported, just like in ActiveRecord itself:
30
-
32
+
31
33
  AutoReplica.using_read_replica_at('sqlite3:/replica_db_145.sqlite3') do
32
34
  ...
33
35
  end
34
-
36
+
37
+ Note that this will create and disconnect a ConnectionPool each time the block is called.
38
+
39
+ The other option is to create the ConnectionPool yourself, and pass it to `using_read_replica_pool`:
40
+
41
+ AutoReplica.using_read_replica_pool(my_connection_pool) do
42
+ ...
43
+ end
44
+
45
+ This will release connections to the pool at the end of the block, but not close them.
46
+
35
47
  To use in Rails controller context (for all actions of this controller):
36
48
 
37
49
  class SlowDataReportsController < ApplicationController
@@ -56,7 +68,7 @@ act accordingly.
56
68
 
57
69
  The `using_read_replica_at` block will allocate a `ConnectionPool` like the standard `ActiveRecord` connection
58
70
  manager does, and the pool is going to be closed and torn down at the end of the block. Since it only uses the basic
59
- ActiveRecord facilities (including mutexes) it should be threadsafe (but _not_ thread-local since the connection
71
+ ActiveRecord facilities (including mutexes) it should be threadsafe (but _not_ thread-local since the connection
60
72
  handler in ActiveRecord isn't).
61
73
 
62
74
  ### Running the specs
@@ -78,7 +90,7 @@ Rails 3.x support is likely to be dropped in the next major version.
78
90
  The gem version is specified in the Rakefile.
79
91
 
80
92
  ### Contributing to activerecord_autoreplica
81
-
93
+
82
94
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
83
95
  * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
84
96
  * Fork the project.
data/Rakefile CHANGED
@@ -13,7 +13,7 @@ require 'rake'
13
13
 
14
14
  require 'jeweler'
15
15
  Jeweler::Tasks.new do |gem|
16
- gem.version = '1.2.0'
16
+ gem.version = '1.3.0'
17
17
  # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
18
18
  gem.name = "activerecord_autoreplica"
19
19
  gem.homepage = "http://github.com/WeTransfer/activerecord_autoreplica"
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: activerecord_autoreplica 1.2.0 ruby lib
5
+ # stub: activerecord_autoreplica 1.3.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "activerecord_autoreplica"
9
- s.version = "1.2.0"
9
+ s.version = "1.3.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Julik Tarkhanov"]
14
- s.date = "2016-10-23"
14
+ s.date = "2016-11-07"
15
15
  s.description = " Redirect all SELECT queries to a separate connection within a block "
16
16
  s.email = "me@julik.nl"
17
17
  s.extra_rdoc_files = [
@@ -14,10 +14,10 @@
14
14
  # * SomeRecord.connection calls ActiveRecord::Base.connection_handler
15
15
  # * It then asks the connection handler for a connection for this specific ActiveRecord subclass, or barring that
16
16
  # - for the connection for one of it's ancestors, ending with ActiveRecord::Base itself
17
- # * The ConnectionHandler, in turn, asks one of it's managed ConnectionPool objects to give it a connection for use.
17
+ # * The ConnectionHandler, in turn, asks one of its managed ConnectionPool objects to give it a connection for use.
18
18
  # * The connection is returned and then the query is ran against it.
19
19
  #
20
- # This is why the only integration point for this is ++ActiveRecord::Base.connection_hanler=++
20
+ # This is why the only integration point for this is ++ActiveRecord::Base.connection_handler=++
21
21
  # To make our trick work, here is what we do:
22
22
  #
23
23
  # First we wrap the original ConnectionHandler used by ActiveRecord in our own proxy. That proxy will ask the original
@@ -41,7 +41,7 @@ module AutoReplica
41
41
  # Runs a given block with all SELECT statements being executed against the read slave
42
42
  # database.
43
43
  #
44
- # AutoReplica.using_read_replica_at(:adapter => 'mysql2', :datbase => 'read_replica', ...) do
44
+ # AutoReplica.using_read_replica_at(:adapter => 'mysql2', :database => 'read_replica', ...) do
45
45
  # customer = Customer.find(3) # Will SELECT from the replica database at the connection spec passed to the block
46
46
  # customer.register_complaint! # Will UPDATE to the master database connection
47
47
  # end
@@ -49,63 +49,49 @@ module AutoReplica
49
49
  # @param replica_connection_spec_hash_or_url[String, Hash] an ActiveRecord connection specification or a DSN URL
50
50
  # @return [void]
51
51
  def self.using_read_replica_at(replica_connection_spec_hash_or_url)
52
+ in_replica_context(replica_connection_spec_hash_or_url, AdHocConnectionHandler){ yield }
53
+ end
54
+
55
+ # Runs a given block with all SELECT statements being executed using the read slave
56
+ # connection pool.
57
+ #
58
+ # read_pool = ActiveRecord::ConnectionAdapters::ConnectionPool.new(:adapter => 'mysql2', :database => 'read_replica', ...)
59
+ # AutoReplica.using_read_replica_pool(read_pool) do
60
+ # customer = Customer.find(3) # Will SELECT from the replica database picked off the read pool
61
+ # customer.register_complaint! # Will UPDATE to the master database connection
62
+ # end
63
+ #
64
+ # @param replica_connection_pool[ActiveRecord::ConnectionAdapters::ConnectionPool] an ActiveRecord connection pool instance
65
+ # @return [void]
66
+ def self.using_read_replica_pool(replica_connection_pool)
67
+ in_replica_context(replica_connection_pool){ yield }
68
+ end
69
+
70
+ def self.in_replica_context(handler_params, handler_class=ConnectionHandler)
52
71
  return yield if @in_replica_context # This method should not be reentrant
53
-
54
- # Resolve if there is a URL given
55
- # Duplicate the hash so that we can change it if we have to
56
- # (say by deleting :adapter)
57
- config_hash = if replica_connection_spec_hash_or_url.is_a?(Hash)
58
- replica_connection_spec_hash_or_url.dup
59
- else
60
- resolve_connection_url(replica_connection_spec_hash_or_url).dup
61
- end
62
-
72
+
63
73
  @in_replica_context = true
64
- # Wrap the connection handler in our proxy
74
+
65
75
  original_connection_handler = ActiveRecord::Base.connection_handler
66
- custom_handler = ConnectionHandler.new(original_connection_handler, config_hash)
76
+ custom_handler = handler_class.new(original_connection_handler, handler_params)
67
77
  begin
68
78
  ActiveRecord::Base.connection_handler = custom_handler
69
79
  yield
70
80
  ensure
71
81
  ActiveRecord::Base.connection_handler = original_connection_handler
72
- custom_handler.disconnect_read_pool!
82
+ custom_handler.finish
73
83
  @in_replica_context = false
74
84
  end
75
85
  end
76
-
77
- # Resolve an ActiveRecord connection URL, from a string to a Hash.
78
- #
79
- # @param url_string[String] the connection URL (like `sqlite3://...`)
80
- # @return [Hash] a symbol-keyed ActiveRecord connection specification
81
- def self.resolve_connection_url(url_string)
82
- # TODO: privatize this method.
83
- if defined?(ActiveRecord::Base::ConnectionSpecification::Resolver) # AR3
84
- resolver = ActiveRecord::Base::ConnectionSpecification::Resolver.new(url_string, {})
85
- resolver.send(:connection_url_to_hash, url_string) # Because making this public was so hard
86
- else # AR4
87
- resolved = ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(url_string).to_hash
88
- resolved["database"].gsub!(/^\//, '') # which is not done by the resolver
89
- resolved.symbolize_keys # which is also not done by the resolver
90
- end
91
- end
92
-
86
+
93
87
  # The connection handler that wraps the ActiveRecord one. Everything gets forwarded to the wrapped
94
88
  # object, but a "spiked" connection adapter gets returned from retrieve_connection.
95
89
  class ConnectionHandler # a proxy for ActiveRecord::ConnectionAdapters::ConnectionHandler
96
- def initialize(original_handler, connection_specification_hash)
90
+ def initialize(original_handler, read_pool)
97
91
  @original_handler = original_handler
98
- # We need to maintain our own pool for read replica connections,
99
- # aside from the one managed by Rails proper.
100
- adapter_method = "%s_connection" % connection_specification_hash[:adapter]
101
- connection_specification = begin
102
- ConnectionSpecification.new('autoreplica', connection_specification_hash, adapter_method)
103
- rescue ArgumentError # AR 4 and lower wants 2 arguments
104
- ConnectionSpecification.new(connection_specification_hash, adapter_method)
105
- end
106
- @read_pool = ActiveRecord::ConnectionAdapters::ConnectionPool.new(connection_specification)
92
+ @read_pool = read_pool
107
93
  end
108
-
94
+
109
95
  # Overridden method which gets called by ActiveRecord to get a connection related to a specific
110
96
  # ActiveRecord::Base subclass.
111
97
  def retrieve_connection(for_ar_class)
@@ -113,18 +99,22 @@ module AutoReplica
113
99
  connection_for_reads = @read_pool.connection
114
100
  Adapter.new(connection_for_writes, connection_for_reads)
115
101
  end
116
-
102
+
103
+ def release_read_pool_connection
104
+ @read_pool.release_connection
105
+ end
106
+
117
107
  # Close all the connections maintained by the read pool
118
108
  def disconnect_read_pool!
119
109
  @read_pool.disconnect!
120
110
  end
121
-
111
+
122
112
  # Disconnect both the original handler AND the read pool
123
113
  def clear_all_connections!
124
114
  disconnect_read_pool!
125
115
  @original_handler.clear_all_connections!
126
116
  end
127
-
117
+
128
118
  # The duo for method proxying without delegate
129
119
  def respond_to_missing?(method_name)
130
120
  @original_handler.respond_to?(method_name)
@@ -132,6 +122,60 @@ module AutoReplica
132
122
  def method_missing(method_name, *args, &blk)
133
123
  @original_handler.public_send(method_name, *args, &blk)
134
124
  end
125
+
126
+ # When finishing, releases the borrowed connection back into the pool
127
+ def finish
128
+ release_read_pool_connection
129
+ end
130
+ end
131
+
132
+ # A connection handler that creates an ad-hoc read connection pool, and disconnects it when finishing
133
+ class AdHocConnectionHandler < ConnectionHandler
134
+ def initialize(original_handler, replica_connection_spec_hash_or_url)
135
+ connection_specification_hash = parse_params(replica_connection_spec_hash_or_url)
136
+ # We need to maintain our own pool for read replica connections,
137
+ # aside from the one managed by Rails proper.
138
+ adapter_method = "%s_connection" % connection_specification_hash[:adapter]
139
+ connection_specification = begin
140
+ ConnectionSpecification.new('autoreplica', connection_specification_hash, adapter_method)
141
+ rescue ArgumentError # AR 4 and lower wants 2 arguments
142
+ ConnectionSpecification.new(connection_specification_hash, adapter_method)
143
+ end
144
+ read_pool = ActiveRecord::ConnectionAdapters::ConnectionPool.new(connection_specification)
145
+ super(original_handler, read_pool)
146
+ end
147
+
148
+ def parse_params(replica_connection_spec_hash_or_url)
149
+ # Resolve if there is a URL given
150
+ # Duplicate the hash so that we can change it if we have to
151
+ # (say by deleting :adapter)
152
+ if replica_connection_spec_hash_or_url.is_a?(Hash)
153
+ replica_connection_spec_hash_or_url.dup
154
+ else
155
+ resolve_connection_url(replica_connection_spec_hash_or_url).dup
156
+ end
157
+ end
158
+
159
+ # Resolve an ActiveRecord connection URL, from a string to a Hash.
160
+ #
161
+ # @param url_string[String] the connection URL (like `sqlite3://...`)
162
+ # @return [Hash] a symbol-keyed ActiveRecord connection specification
163
+ def resolve_connection_url(url_string)
164
+ # TODO: privatize this method.
165
+ if defined?(ActiveRecord::Base::ConnectionSpecification::Resolver) # AR3
166
+ resolver = ActiveRecord::Base::ConnectionSpecification::Resolver.new(url_string, {})
167
+ resolver.send(:connection_url_to_hash, url_string) # Because making this public was so hard
168
+ else # AR4
169
+ resolved = ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(url_string).to_hash
170
+ resolved["database"].gsub!(/^\//, '') # which is not done by the resolver
171
+ resolved.symbolize_keys # which is also not done by the resolver
172
+ end
173
+ end
174
+
175
+ # Disconnect all read pool connections, making the pool ready to be disposed.
176
+ def finish
177
+ disconnect_read_pool!
178
+ end
135
179
  end
136
180
 
137
181
  # Acts as a wrapping proxy that replaces an ActiveRecord Adapter object. This is the
@@ -146,7 +190,7 @@ module AutoReplica
146
190
  @master_connection = master_connection_adapter
147
191
  @read_connection = replica_connection_adapter
148
192
  end
149
-
193
+
150
194
  # Under the hood, ActiveRecord uses methods for the most common database statements
151
195
  # like "select_all", "select_one", "select_value" and so on. Those can be overridden by concrete
152
196
  # connection adapters, but in the basic abstract Adapter they get included from
@@ -160,7 +204,7 @@ module AutoReplica
160
204
  @read_connection.send(select_method_name, *method_arguments)
161
205
  end
162
206
  end
163
-
207
+
164
208
  # The duo for method proxying without delegate
165
209
  def respond_to_missing?(method_name)
166
210
  @master_connection.respond_to?(method_name)
@@ -169,11 +213,11 @@ module AutoReplica
169
213
  @master_connection.public_send(method_name, *args, &blk)
170
214
  end
171
215
  end
172
-
216
+
173
217
  # if respond_to?(:private_constant)
174
- # private_constant :ConnectionSpecification
218
+ # private_constant :ConnectionSpecification
175
219
  # private_constant :ConnectionHandler
176
220
  # private_constant :Adapter
177
221
  # end
178
-
222
+
179
223
  end
@@ -2,16 +2,16 @@ require_relative 'helper'
2
2
  require 'securerandom'
3
3
 
4
4
  describe AutoReplica do
5
-
5
+
6
6
  before :all do
7
7
  test_seed_name = SecureRandom.hex(4)
8
8
  ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ('master_db_%s.sqlite3' % test_seed_name))
9
-
9
+
10
10
  # Setup the master and replica connections
11
11
  @master_connection_config = ActiveRecord::Base.connection_config.dup
12
12
  @replica_connection_config = @master_connection_config.merge(database: ('replica_db_%s.sqlite3' % test_seed_name))
13
13
  @replica_connection_config_url = 'sqlite3:/replica_db_%s.sqlite3' % test_seed_name
14
-
14
+
15
15
  ActiveRecord::Migration.suppress_messages do
16
16
  # Create both the master and the replica, with a simple small schema
17
17
  [@master_connection_config, @replica_connection_config].each do | db_config |
@@ -25,25 +25,25 @@ describe AutoReplica do
25
25
  end
26
26
  end
27
27
  end
28
-
28
+
29
29
  after :all do
30
30
  # Ensure database files get killed afterwards
31
31
  [@master_connection_config, @replica_connection_config].map do | connection_config |
32
32
  File.unlink(connection_config[:database]) rescue nil
33
33
  end
34
34
  end
35
-
35
+
36
36
  before :each do
37
37
  [@replica_connection_config, @master_connection_config].each do | config |
38
38
  ActiveRecord::Base.establish_connection(config)
39
39
  ActiveRecord::Base.connection.execute 'DELETE FROM things' # sqlite has no TRUNCATE
40
40
  end
41
41
  end
42
-
42
+
43
43
  class TestThing < ActiveRecord::Base
44
44
  self.table_name = 'things'
45
45
  end
46
-
46
+
47
47
  context 'using_read_replica_at' do
48
48
  it 'has no reentrancy problems' do
49
49
  id = described_class.using_read_replica_at(@replica_connection_config) do
@@ -53,7 +53,7 @@ describe AutoReplica do
53
53
  expect {
54
54
  TestThing.find(thing.id)
55
55
  }.to raise_error(ActiveRecord::RecordNotFound)
56
-
56
+
57
57
  thing.id # return to the outside of the block
58
58
  end
59
59
  end
@@ -61,7 +61,7 @@ describe AutoReplica do
61
61
  found_on_master = TestThing.find(id)
62
62
  expect(found_on_master.description).to eq('A nice Thing in the master database')
63
63
  end
64
-
64
+
65
65
  it 'executes the SELECT query against the replica database and returns the result of the block' do
66
66
  id = described_class.using_read_replica_at(@replica_connection_config) do
67
67
  thing = TestThing.create! description: 'A nice Thing in the master database'
@@ -72,20 +72,20 @@ describe AutoReplica do
72
72
  end
73
73
  found_on_master = TestThing.find(id)
74
74
  expect(found_on_master.description).to eq('A nice Thing in the master database')
75
-
75
+
76
76
  ActiveRecord::Base.establish_connection(@replica_connection_config)
77
77
  thing_on_slave = TestThing.new(found_on_master.attributes)
78
78
  thing_on_slave.id = found_on_master.id # gets ignored in attributes
79
79
  thing_on_slave.description = 'A nice Thing that is in the slave database'
80
80
  thing_on_slave.save!
81
-
81
+
82
82
  ActiveRecord::Base.establish_connection(@master_connection_config)
83
83
  described_class.using_read_replica_at(@replica_connection_config) do
84
84
  thing_from_replica = TestThing.find(id)
85
85
  expect(thing_from_replica.description).to eq('A nice Thing that is in the slave database')
86
86
  end
87
87
  end
88
-
88
+
89
89
  it 'executes the SELECT query against the replica database with replica connection specification given as a URL' do
90
90
  id = described_class.using_read_replica_at(@replica_connection_config_url) do
91
91
  thing = TestThing.create! description: 'A nice Thing in the master database'
@@ -98,77 +98,120 @@ describe AutoReplica do
98
98
  expect(found_on_master.description).to eq('A nice Thing in the master database')
99
99
  end
100
100
  end
101
-
101
+
102
102
  describe AutoReplica::ConnectionHandler do
103
+ it 'proxies all methods' do
104
+ original_handler = double('ActiveRecord_ConnectionHandler')
105
+ expect(original_handler).to receive(:do_that_thing) { :yes }
106
+ pool_double = double('ConnectionPool')
107
+ subject = AutoReplica::ConnectionHandler.new(original_handler, pool_double)
108
+ expect(subject.do_that_thing).to eq(:yes)
109
+ end
110
+
111
+ it 'enhances connection_for and returns an instance of the Adapter' do
112
+ original_handler = double('ActiveRecord_ConnectionHandler')
113
+ adapter_double = double('ActiveRecord_Adapter')
114
+ connection_double = double('Connection')
115
+ pool_double = double('ConnectionPool')
116
+ expect(original_handler).to receive(:retrieve_connection).with(TestThing) { adapter_double }
117
+ expect(pool_double).to receive(:connection) { connection_double }
118
+
119
+ subject = AutoReplica::ConnectionHandler.new(original_handler, pool_double)
120
+ connection = subject.retrieve_connection(TestThing)
121
+ expect(connection).to be_kind_of(AutoReplica::Adapter)
122
+ end
123
+
124
+ it 'releases the the read pool connection when finishing' do
125
+ original_handler = double('ActiveRecord_ConnectionHandler')
126
+ pool_double = double('ConnectionPool')
127
+ subject = AutoReplica::ConnectionHandler.new(original_handler, pool_double)
128
+
129
+ expect(pool_double).to receive(:release_connection)
130
+ subject.finish
131
+ end
132
+
133
+ it 'performs clear_all_connections! both on the contained handler and on the read pool' do
134
+ original_handler = double('ActiveRecord_ConnectionHandler')
135
+ pool_double = double('ConnectionPool')
136
+
137
+ expect(original_handler).to receive(:clear_all_connections!)
138
+ expect(pool_double).to receive(:disconnect!)
139
+
140
+ subject = AutoReplica::ConnectionHandler.new(original_handler, pool_double)
141
+ subject.clear_all_connections!
142
+ end
143
+ end
144
+
145
+ describe AutoReplica::AdHocConnectionHandler do
103
146
  it 'creates a read pool with the replica connection specification hash' do
104
147
  original_handler = double('ActiveRecord_ConnectionHandler')
105
148
  expect(ActiveRecord::ConnectionAdapters::ConnectionPool).to receive(:new).
106
149
  with(instance_of(AutoReplica::ConnectionSpecification))
107
-
108
- AutoReplica::ConnectionHandler.new(original_handler, @replica_connection_config)
150
+
151
+ AutoReplica::AdHocConnectionHandler.new(original_handler, @replica_connection_config)
109
152
  end
110
-
153
+
111
154
  it 'proxies all methods' do
112
155
  original_handler = double('ActiveRecord_ConnectionHandler')
113
156
  expect(original_handler).to receive(:do_that_thing) { :yes }
114
- subject = AutoReplica::ConnectionHandler.new(original_handler, @replica_connection_config)
157
+ subject = AutoReplica::AdHocConnectionHandler.new(original_handler, @replica_connection_config)
115
158
  expect(subject.do_that_thing).to eq(:yes)
116
159
  end
117
-
160
+
118
161
  it 'enhances connection_for and returns an instance of the Adapter' do
119
162
  original_handler = double('ActiveRecord_ConnectionHandler')
120
163
  adapter_double = double('ActiveRecord_Adapter')
121
164
  expect(original_handler).to receive(:retrieve_connection).with(TestThing) { adapter_double }
122
-
123
- subject = AutoReplica::ConnectionHandler.new(original_handler, @replica_connection_config)
165
+
166
+ subject = AutoReplica::AdHocConnectionHandler.new(original_handler, @replica_connection_config)
124
167
  connection = subject.retrieve_connection(TestThing)
125
168
  expect(connection).to be_kind_of(AutoReplica::Adapter)
126
169
  end
127
-
128
- it 'disconnects the read pool when asked to' do
170
+
171
+ it 'disconnects the read pool when finishing' do
129
172
  original_handler = double('ActiveRecord_ConnectionHandler')
130
173
  pool_double = double('ConnectionPool')
131
174
  expect(ActiveRecord::ConnectionAdapters::ConnectionPool).to receive(:new).
132
175
  with(instance_of(AutoReplica::ConnectionSpecification)) { pool_double }
133
- subject = AutoReplica::ConnectionHandler.new(original_handler, @replica_connection_config)
134
-
176
+ subject = AutoReplica::AdHocConnectionHandler.new(original_handler, @replica_connection_config)
177
+
135
178
  expect(pool_double).to receive(:disconnect!)
136
- subject.disconnect_read_pool!
179
+ subject.finish
137
180
  end
138
-
181
+
139
182
  it 'performs clear_all_connections! both on the contained handler and on the read pool' do
140
183
  original_handler = double('ActiveRecord_ConnectionHandler')
141
184
  pool_double = double('ConnectionPool')
142
185
  expect(ActiveRecord::ConnectionAdapters::ConnectionPool).to receive(:new).
143
186
  with(instance_of(AutoReplica::ConnectionSpecification)) { pool_double }
144
-
187
+
145
188
  expect(original_handler).to receive(:clear_all_connections!)
146
189
  expect(pool_double).to receive(:disconnect!)
147
-
148
- subject = AutoReplica::ConnectionHandler.new(original_handler, @replica_connection_config)
190
+
191
+ subject = AutoReplica::AdHocConnectionHandler.new(original_handler, @replica_connection_config)
149
192
  subject.clear_all_connections!
150
193
  end
151
194
  end
152
-
195
+
153
196
  describe AutoReplica::Adapter do
154
197
  it 'mirrors select_ prefixed database statement methods in ActiveRecord::ConnectionAdapters::DatabaseStatements' do
155
198
  master = double()
156
199
  expect(master).not_to receive(:respond_to?)
157
200
  subject = AutoReplica::Adapter.new(master, double())
158
-
201
+
159
202
  select_methods = ActiveRecord::ConnectionAdapters::DatabaseStatements.instance_methods.grep(/^select_/)
160
203
  expect(select_methods.length).to be > 1
161
-
204
+
162
205
  select_methods.each do | select_method_in_database_statements |
163
206
  expect(subject).to respond_to(select_method_in_database_statements)
164
207
  end
165
208
  end
166
-
209
+
167
210
  it 'redirects calls to all select_ methods to the read connection and others to the master connection' do
168
211
  master_adapter = double('Connection to the master DB')
169
212
  replica_adapter = double('Connection to the replica DB')
170
213
  subject = AutoReplica::Adapter.new(master_adapter, replica_adapter)
171
-
214
+
172
215
  expect(master_adapter).to receive(:some_arbitrary_method) { :from_master }
173
216
  expect(replica_adapter).to receive(:select_values).with(:a, :b, :c) { :from_replica }
174
217
  expect(subject.some_arbitrary_method).to eq(:from_master)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord_autoreplica
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-23 00:00:00.000000000 Z
11
+ date: 2016-11-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord