ar-multidb 0.5.1 → 0.7.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 +4 -4
- data/.github/workflows/prs.yml +71 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +149 -5
- data/.simplecov +22 -0
- data/CHANGELOG.md +9 -1
- data/Gemfile +2 -5
- data/README.markdown +4 -2
- data/ar-multidb.gemspec +11 -7
- data/gemfiles/activerecord-6.1.gemfile +7 -0
- data/gemfiles/activerecord-7.0.gemfile +7 -0
- data/gemfiles/activerecord-7.1.gemfile +7 -0
- data/lib/multidb/balancer.rb +2 -2
- data/lib/multidb/candidate.rb +12 -3
- data/lib/multidb/configuration.rb +0 -22
- data/lib/multidb/log_subscriber.rb +2 -2
- data/lib/multidb/model_extensions.rb +7 -1
- data/lib/multidb/version.rb +1 -1
- data/lib/multidb.rb +26 -0
- data/spec/lib/multidb/balancer_spec.rb +393 -0
- data/spec/lib/multidb/candidate_spec.rb +105 -0
- data/spec/lib/multidb/configuration_spec.rb +23 -0
- data/spec/lib/multidb/log_subscriber_extension_spec.rb +89 -0
- data/spec/lib/multidb/model_extensions_spec.rb +61 -0
- data/spec/lib/multidb_spec.rb +91 -0
- data/spec/spec_helper.rb +30 -5
- data/spec/support/have_database_matcher.rb +15 -0
- data/spec/{helpers.rb → support/helpers.rb} +4 -1
- metadata +80 -21
- data/.travis.yml +0 -17
- data/spec/balancer_spec.rb +0 -161
- /data/gemfiles/{activerecord51.gemfile → activerecord-5.1.gemfile} +0 -0
- /data/gemfiles/{activerecord52.gemfile → activerecord-5.2.gemfile} +0 -0
- /data/gemfiles/{activerecord60.gemfile → activerecord-6.0.gemfile} +0 -0
@@ -0,0 +1,393 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Multidb::Balancer do
|
6
|
+
let(:config) { configuration_with_replicas }
|
7
|
+
let(:configuration) {
|
8
|
+
c = config.with_indifferent_access
|
9
|
+
Multidb::Configuration.new(c.except(:multidb), c[:multidb] || {})
|
10
|
+
}
|
11
|
+
let(:balancer) { global_config ? Multidb.balancer : described_class.new(configuration) }
|
12
|
+
let(:global_config) { false }
|
13
|
+
|
14
|
+
before do
|
15
|
+
ActiveRecord::Base.establish_connection(config) if global_config
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#initialize' do
|
19
|
+
subject { balancer }
|
20
|
+
|
21
|
+
context 'when configuration has no multidb config' do
|
22
|
+
let(:config) { configuration_with_replicas.except('multidb') }
|
23
|
+
|
24
|
+
it 'sets @candidates to have only default set of candidates' do
|
25
|
+
expect(subject.instance_variable_get(:@candidates).keys).to contain_exactly('default')
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'sets @default_candidate to be the fist candidate for the default @candidates' do
|
29
|
+
candidates = subject.instance_variable_get(:@candidates)
|
30
|
+
|
31
|
+
expect(subject.instance_variable_get(:@default_candidate)).to eq(candidates['default'].first)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'sets @default_configuration to be the configuration' do
|
35
|
+
expect(subject.instance_variable_get(:@default_configuration)).to eq(configuration)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'sets fallback to false' do
|
39
|
+
expect(subject.fallback).to eq(false)
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when rails ENV is development' do
|
43
|
+
before do
|
44
|
+
stub_const('Rails', class_double('Rails', env: 'development'))
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'sets fallback to true' do
|
48
|
+
expect(subject.fallback).to eq(true)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when rails ENV is test' do
|
53
|
+
before do
|
54
|
+
stub_const('Rails', class_double('Rails', env: 'test'))
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'sets fallback to true' do
|
58
|
+
expect(subject.fallback).to eq(true)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'when configuration has fallback: true' do
|
64
|
+
let(:config) { configuration_with_replicas.merge('multidb' => { 'fallback' => true }) }
|
65
|
+
|
66
|
+
it 'sets @candidates to have only default set of candidates' do
|
67
|
+
expect(subject.instance_variable_get(:@candidates).keys).to contain_exactly('default')
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'sets @default_candidate to be the fist candidate for the default @candidates' do
|
71
|
+
candidates = subject.instance_variable_get(:@candidates)
|
72
|
+
|
73
|
+
expect(subject.instance_variable_get(:@default_candidate)).to eq(candidates['default'].first)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'sets @default_configuration to be the configuration' do
|
77
|
+
expect(subject.instance_variable_get(:@default_configuration)).to eq(configuration)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'sets fallback to true' do
|
81
|
+
expect(subject.fallback).to eq(true)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'when configuration has default multidb configuration' do
|
86
|
+
let(:config) {
|
87
|
+
extra = { multidb: { databases: {
|
88
|
+
default: {
|
89
|
+
adapter: 'sqlite3',
|
90
|
+
database: 'spec/test-default.sqlite'
|
91
|
+
}
|
92
|
+
} } }
|
93
|
+
configuration_with_replicas.merge(extra)
|
94
|
+
}
|
95
|
+
|
96
|
+
it 'set @candidates default to that configuration and not @default_candidate' do
|
97
|
+
candidates = subject.instance_variable_get(:@candidates)
|
98
|
+
default_candidate = subject.instance_variable_get(:@default_candidate)
|
99
|
+
|
100
|
+
expect(candidates[:default].first).not_to eq default_candidate
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'when configuration is nil' do
|
105
|
+
let(:configuration) { nil }
|
106
|
+
|
107
|
+
it 'set @candidates to an empty hash' do
|
108
|
+
expect(subject.instance_variable_get(:@candidates)).to eq({})
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe '#append' do
|
114
|
+
subject { balancer.append(appended_config) }
|
115
|
+
|
116
|
+
let(:config) { configuration_with_replicas.except('multidb') }
|
117
|
+
|
118
|
+
context 'with a basic configuration' do
|
119
|
+
let(:appended_config) { { replica4: { database: 'spec/test-replica4.sqlite' } } }
|
120
|
+
|
121
|
+
it 'registers the new candidate set in @candidates' do
|
122
|
+
expect { subject }.to change {
|
123
|
+
balancer.instance_variable_get(:@candidates)
|
124
|
+
}.to include('replica4')
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'makes it available for use' do
|
128
|
+
subject
|
129
|
+
|
130
|
+
balancer.use(:replica4) do
|
131
|
+
expect(balancer.current_connection).to have_database 'test-replica4.sqlite'
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'returns the connection name' do
|
136
|
+
subject
|
137
|
+
|
138
|
+
balancer.use(:replica4) do
|
139
|
+
expect(balancer.current_connection_name).to eq :replica4
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context 'with an alias' do
|
145
|
+
let(:appended_config) {
|
146
|
+
{
|
147
|
+
replica2: {
|
148
|
+
database: 'spec/test-replica4.sqlite'
|
149
|
+
},
|
150
|
+
replica_alias: {
|
151
|
+
alias: 'replica2'
|
152
|
+
}
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
it 'aliases replica4 as replica2' do
|
157
|
+
subject
|
158
|
+
|
159
|
+
candidates = balancer.instance_variable_get(:@candidates)
|
160
|
+
|
161
|
+
expect(candidates['replica2']).to eq(candidates['replica_alias'])
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe '#disconnect!' do
|
167
|
+
subject { balancer.disconnect! }
|
168
|
+
|
169
|
+
it 'calls disconnect! on all the candidates' do
|
170
|
+
candidate1 = instance_double(Multidb::Candidate, disconnect!: nil)
|
171
|
+
candidate2 = instance_double(Multidb::Candidate, disconnect!: nil)
|
172
|
+
|
173
|
+
candidates = { 'replica1' => [candidate1], 'replica2' => [candidate2] }
|
174
|
+
|
175
|
+
balancer.instance_variable_set(:@candidates, candidates)
|
176
|
+
|
177
|
+
subject
|
178
|
+
|
179
|
+
expect(candidate1).to have_received(:disconnect!)
|
180
|
+
expect(candidate2).to have_received(:disconnect!)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe '#get' do
|
185
|
+
subject { balancer.get(name) }
|
186
|
+
|
187
|
+
let(:name) { :replica1 }
|
188
|
+
let(:candidates) { balancer.instance_variable_get(:@candidates) }
|
189
|
+
|
190
|
+
context 'when there is only one candidate' do
|
191
|
+
it 'returns the candidate' do
|
192
|
+
is_expected.to eq candidates['replica1'].first
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
context 'when there is more than one candidate' do
|
197
|
+
it 'returns a random candidate' do
|
198
|
+
returned = Set.new
|
199
|
+
100.times do
|
200
|
+
returned << balancer.get(:replica3)
|
201
|
+
end
|
202
|
+
|
203
|
+
expect(returned).to match_array candidates['replica3']
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
context 'when the name has no configuration' do
|
208
|
+
let(:name) { :other }
|
209
|
+
|
210
|
+
context 'when fallback is false' do
|
211
|
+
it 'raises an error' do
|
212
|
+
expect { subject }.to raise_error(ArgumentError, /No such database connection/)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
context 'when fallback is true' do
|
217
|
+
before do
|
218
|
+
balancer.fallback = true
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'returns the default connection' do
|
222
|
+
is_expected.to eq candidates[:default].first
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
context 'when given a block' do
|
227
|
+
it 'yields the candidate' do
|
228
|
+
expect { |y|
|
229
|
+
balancer.get(:replica1, &y)
|
230
|
+
}.to yield_with_args(candidates[:replica1].first)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
describe '#use' do
|
237
|
+
context 'with an undefined connection' do
|
238
|
+
it 'raises exception' do
|
239
|
+
expect {
|
240
|
+
balancer.use(:something) { nil }
|
241
|
+
}.to raise_error(ArgumentError)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
context 'with a configured connection' do
|
246
|
+
let(:global_config) { true }
|
247
|
+
|
248
|
+
it 'returns default connection on :default' do
|
249
|
+
balancer.use(:default) do
|
250
|
+
expect(balancer.current_connection).to have_database 'test.sqlite'
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'returns results instead of relation' do
|
255
|
+
foobar_class = Class.new(ActiveRecord::Base) do
|
256
|
+
self.table_name = 'foo_bars'
|
257
|
+
end
|
258
|
+
|
259
|
+
res = balancer.use(:replica1) do
|
260
|
+
ActiveRecord::Migration.verbose = false
|
261
|
+
ActiveRecord::Schema.define(version: 1) { create_table :foo_bars }
|
262
|
+
foobar_class.where(id: 42)
|
263
|
+
end
|
264
|
+
|
265
|
+
expect(res).to eq []
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'returns replica connection' do
|
270
|
+
balancer.use(:replica1) do
|
271
|
+
expect(balancer.current_connection).to have_database 'test-replica1.sqlite'
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'returns supports nested replica connection' do
|
276
|
+
balancer.use(:replica1) do
|
277
|
+
balancer.use(:replica2) do
|
278
|
+
expect(balancer.current_connection).to have_database 'test-replica2.sqlite'
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
it 'returns preserves state when nesting' do
|
284
|
+
balancer.use(:replica1) do
|
285
|
+
balancer.use(:replica2) do
|
286
|
+
expect(balancer.current_connection).to have_database 'test-replica2.sqlite'
|
287
|
+
end
|
288
|
+
|
289
|
+
expect(balancer.current_connection).to have_database 'test-replica1.sqlite'
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
it 'returns the parent connection for aliases' do
|
294
|
+
expect(balancer.use(:replica1)).not_to eq balancer.use(:replica_alias)
|
295
|
+
expect(balancer.use(:replica2)).to eq balancer.use(:replica_alias)
|
296
|
+
end
|
297
|
+
|
298
|
+
context 'when there are multiple candidates' do
|
299
|
+
it 'returns random candidate' do
|
300
|
+
names = []
|
301
|
+
100.times do
|
302
|
+
balancer.use(:replica3) do
|
303
|
+
list = balancer.current_connection.execute('pragma database_list')
|
304
|
+
names.push(File.basename(list.first&.[]('file')))
|
305
|
+
end
|
306
|
+
end
|
307
|
+
expect(names.uniq).to match_array %w[test-replica3-1.sqlite test-replica3-2.sqlite]
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
describe '#current_connection' do
|
313
|
+
subject { balancer.current_connection }
|
314
|
+
|
315
|
+
context 'when no alternate connection is active' do
|
316
|
+
let(:global_config) { true }
|
317
|
+
|
318
|
+
it 'returns main connection by default' do
|
319
|
+
is_expected.to have_database 'test.sqlite'
|
320
|
+
|
321
|
+
is_expected.to eq ActiveRecord::Base.retrieve_connection
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
context 'when an alternate connection is active' do
|
326
|
+
before do
|
327
|
+
Thread.current[:multidb] = { connection: 'a different connection' }
|
328
|
+
end
|
329
|
+
|
330
|
+
it 'returns the thread local connection' do
|
331
|
+
is_expected.to eq 'a different connection'
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
describe '#current_connection_name' do
|
337
|
+
subject { balancer.current_connection_name }
|
338
|
+
|
339
|
+
context 'when no alternate connection is active' do
|
340
|
+
it 'returns default connection name for default connection' do
|
341
|
+
is_expected.to eq :default
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
context 'when an alternate connection is active' do
|
346
|
+
before do
|
347
|
+
Thread.current[:multidb] = { connection_name: :replica1 }
|
348
|
+
end
|
349
|
+
|
350
|
+
it 'returns the thread local connection' do
|
351
|
+
is_expected.to eq :replica1
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
describe 'class delegates' do
|
357
|
+
let(:balancer) {
|
358
|
+
instance_double('Multidb::Balancer',
|
359
|
+
use: nil,
|
360
|
+
current_connection: nil,
|
361
|
+
current_connection_name: nil,
|
362
|
+
disconnect!: nil)
|
363
|
+
}
|
364
|
+
|
365
|
+
before do
|
366
|
+
Multidb.instance_variable_set(:@balancer, balancer)
|
367
|
+
end
|
368
|
+
|
369
|
+
it 'delegates use to the balancer' do
|
370
|
+
described_class.use(:name)
|
371
|
+
|
372
|
+
expect(balancer).to have_received(:use).with(:name)
|
373
|
+
end
|
374
|
+
|
375
|
+
it 'delegates current_connection to the balancer' do
|
376
|
+
described_class.current_connection
|
377
|
+
|
378
|
+
expect(balancer).to have_received(:current_connection)
|
379
|
+
end
|
380
|
+
|
381
|
+
it 'delegates current_connection_name to the balancer' do
|
382
|
+
described_class.current_connection_name
|
383
|
+
|
384
|
+
expect(balancer).to have_received(:current_connection_name)
|
385
|
+
end
|
386
|
+
|
387
|
+
it 'delegates disconnect! to the balancer' do
|
388
|
+
described_class.disconnect!
|
389
|
+
|
390
|
+
expect(balancer).to have_received(:disconnect!)
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Multidb::Candidate do
|
6
|
+
subject(:candidate) { described_class.new(name, target) }
|
7
|
+
|
8
|
+
let(:name) { :default }
|
9
|
+
let(:config) { configuration_with_replicas.with_indifferent_access.except(:multidb) }
|
10
|
+
let(:target) { config }
|
11
|
+
|
12
|
+
describe '#initialize' do
|
13
|
+
context 'when target is a config hash' do
|
14
|
+
let(:target) { config }
|
15
|
+
|
16
|
+
it 'sets the connection_handler to a new AR connection handler' do
|
17
|
+
handler = subject.instance_variable_get(:@connection_handler)
|
18
|
+
expect(handler).to an_instance_of(ActiveRecord::ConnectionAdapters::ConnectionHandler)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'merges the name: primary into the hash', rails: '< 6.1' do
|
22
|
+
handler = instance_double('ActiveRecord::ConnectionAdapters::ConnectionHandler')
|
23
|
+
allow(ActiveRecord::ConnectionAdapters::ConnectionHandler).to receive(:new).and_return(handler)
|
24
|
+
allow(handler).to receive(:establish_connection)
|
25
|
+
|
26
|
+
subject
|
27
|
+
|
28
|
+
expect(handler).to have_received(:establish_connection).with(hash_including(name: 'primary'))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when target is a connection handler' do
|
33
|
+
let(:target) { ActiveRecord::ConnectionAdapters::ConnectionHandler.new }
|
34
|
+
|
35
|
+
it 'sets the connection_handler to the passed handler' do
|
36
|
+
handler = subject.instance_variable_get(:@connection_handler)
|
37
|
+
expect(handler).to eq(target)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when target is anything else' do
|
42
|
+
let(:target) { 'something else' }
|
43
|
+
|
44
|
+
it 'raises an ArgumentError' do
|
45
|
+
expect { subject }.to raise_error(ArgumentError, /Connection handler not passed/)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'sets the name to the name' do
|
50
|
+
expect(subject.name).to eq name
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#connection' do
|
55
|
+
let(:pool) {
|
56
|
+
instance_double('ActiveRecord::ConnectionAdapters::ConnectionPool').tap do |o|
|
57
|
+
allow(o).to receive(:with_connection).and_yield('a connection')
|
58
|
+
end
|
59
|
+
}
|
60
|
+
let(:target) {
|
61
|
+
ActiveRecord::ConnectionAdapters::ConnectionHandler.new.tap do |o|
|
62
|
+
allow(o).to receive(:retrieve_connection)
|
63
|
+
allow(o).to receive(:retrieve_connection_pool).and_return(pool)
|
64
|
+
end
|
65
|
+
}
|
66
|
+
|
67
|
+
context 'when given a block' do
|
68
|
+
it 'calls retrieve_connection_pool' do
|
69
|
+
subject.connection { |_| nil }
|
70
|
+
|
71
|
+
expect(target).to have_received(:retrieve_connection_pool).with(Multidb::Candidate::SPEC_NAME)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'yields a connection object' do
|
75
|
+
expect { |y|
|
76
|
+
subject.connection(&y)
|
77
|
+
}.to yield_with_args('a connection')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'when not given a block' do
|
82
|
+
it 'calls retrieve_connection on the handler' do
|
83
|
+
subject.connection
|
84
|
+
|
85
|
+
expect(target).to have_received(:retrieve_connection).with(Multidb::Candidate::SPEC_NAME)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#disconnect!' do
|
91
|
+
subject { candidate.disconnect! }
|
92
|
+
|
93
|
+
let(:target) {
|
94
|
+
ActiveRecord::ConnectionAdapters::ConnectionHandler.new.tap do |o|
|
95
|
+
allow(o).to receive(:clear_all_connections!)
|
96
|
+
end
|
97
|
+
}
|
98
|
+
|
99
|
+
it 'calls clear_all_connections! on the handler' do
|
100
|
+
subject
|
101
|
+
|
102
|
+
expect(target).to have_received(:clear_all_connections!)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Multidb::Configuration do
|
6
|
+
subject { described_class.new(config.except(:multidb), config[:multidb]) }
|
7
|
+
|
8
|
+
let(:config) { configuration_with_replicas.with_indifferent_access }
|
9
|
+
|
10
|
+
describe '#initialize' do
|
11
|
+
it 'sets the default_handler to the AR connection handler' do
|
12
|
+
expect(subject.default_handler).to eq(ActiveRecord::Base.connection_handler)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'sets the default_adapter to the main configuration' do
|
16
|
+
expect(subject.default_adapter).to eq config.except(:multidb)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'sets the raw_configuration to the multidb configuration' do
|
20
|
+
expect(subject.raw_configuration).to eq config[:multidb]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Multidb::LogSubscriberExtension do
|
6
|
+
before do
|
7
|
+
ActiveRecord::Base.establish_connection(configuration_with_replicas)
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:klass) {
|
11
|
+
klass = Class.new do
|
12
|
+
def sql(event)
|
13
|
+
event
|
14
|
+
end
|
15
|
+
|
16
|
+
def debug(msg)
|
17
|
+
msg
|
18
|
+
end
|
19
|
+
|
20
|
+
def color(text, _color, _bold)
|
21
|
+
text
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
klass.tap do |o|
|
26
|
+
o.prepend described_class
|
27
|
+
end
|
28
|
+
}
|
29
|
+
|
30
|
+
let(:instance) { klass.new }
|
31
|
+
|
32
|
+
it 'prepends the extension into the ActiveRecord::LogSubscriber' do
|
33
|
+
expect(ActiveRecord::LogSubscriber.included_modules).to include(described_class)
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#sql' do
|
37
|
+
subject { instance.sql(event) }
|
38
|
+
|
39
|
+
let(:event) { instance_double('Event', payload: {}) }
|
40
|
+
|
41
|
+
it 'sets the :default db_name into the event payload' do
|
42
|
+
expect { subject }.to change { event.payload }.to include(db_name: :default)
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when a replica is active' do
|
46
|
+
it 'sets the db_name into the event payload to the replica' do
|
47
|
+
expect {
|
48
|
+
Multidb.use(:replica1) { subject }
|
49
|
+
}.to change { event.payload }.to include(db_name: :replica1)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'when there is no name returned from the balancer' do
|
54
|
+
before do
|
55
|
+
allow(Multidb.balancer).to receive(:current_connection_name)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'does not change the payload' do
|
59
|
+
expect { subject }.not_to change { event.payload }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#debug' do
|
65
|
+
subject { instance.debug('message') }
|
66
|
+
|
67
|
+
it 'prepends the db name to the message' do
|
68
|
+
is_expected.to include('[DB: default]')
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when a replica is active' do
|
72
|
+
it 'prepends the replica dbname to the message' do
|
73
|
+
Multidb.use(:replica1) {
|
74
|
+
is_expected.to include('[DB: replica1')
|
75
|
+
}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'when there is no name returned from the balancer' do
|
80
|
+
before do
|
81
|
+
allow(Multidb.balancer).to receive(:current_connection_name)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'does not prepend to the message' do
|
85
|
+
is_expected.to eq('message')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Multidb::ModelExtensions do
|
6
|
+
it 'includes the Multidb::Connection module into the class methods of ActiveRecord::Base' do
|
7
|
+
expect(ActiveRecord::Base.singleton_class.included_modules).to include Multidb::Connection
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Multidb::Connection do
|
11
|
+
describe '.establish_connection' do
|
12
|
+
subject { ActiveRecord::Base.establish_connection(configuration_with_replicas) }
|
13
|
+
|
14
|
+
it 'initializes multidb' do
|
15
|
+
allow(Multidb).to receive(:init)
|
16
|
+
|
17
|
+
subject
|
18
|
+
|
19
|
+
expect(Multidb).to have_received(:init)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '.connection' do
|
24
|
+
subject { klass.connection }
|
25
|
+
|
26
|
+
let(:klass) {
|
27
|
+
Class.new do
|
28
|
+
def self.connection
|
29
|
+
'AR connection'
|
30
|
+
end
|
31
|
+
|
32
|
+
include Multidb::ModelExtensions
|
33
|
+
end
|
34
|
+
}
|
35
|
+
|
36
|
+
context 'when multidb is not initialized' do
|
37
|
+
it 'calls AR::Base.connection' do
|
38
|
+
is_expected.to eq('AR connection')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when multidb is initialized' do
|
43
|
+
let(:balancer) { instance_double('Multidb::Balancer', current_connection: 'Multidb connection') }
|
44
|
+
|
45
|
+
before do
|
46
|
+
Multidb.instance_variable_set(:@balancer, balancer)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'calls current_connection on the balancer' do
|
50
|
+
subject
|
51
|
+
|
52
|
+
expect(balancer).to have_received(:current_connection)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'returns the balancer connection' do
|
56
|
+
is_expected.to eq('Multidb connection')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|