master_slave_adapter 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.
- data/.gitignore +5 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +3 -0
- data/LICENSE +23 -0
- data/Rakefile +10 -0
- data/Readme.md +181 -0
- data/TODO.txt +8 -0
- data/VERSION +1 -0
- data/lib/active_record/connection_adapters/master_slave_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/mysql_master_slave_adapter.rb +1 -0
- data/lib/master_slave_adapter.rb +624 -0
- data/master_slave_adapter.gemspec +25 -0
- data/spec/master_slave_adapter_spec.rb +563 -0
- metadata +111 -0
@@ -0,0 +1,563 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_record'
|
3
|
+
require 'rspec'
|
4
|
+
|
5
|
+
ActiveRecord::Base.logger =
|
6
|
+
Logger.new(STDOUT).tap { |l| l.level = Logger::DEBUG }
|
7
|
+
|
8
|
+
$LOAD_PATH << File.expand_path(File.join( File.dirname( __FILE__ ), '..', 'lib' ))
|
9
|
+
|
10
|
+
require 'active_record/connection_adapters/master_slave_adapter'
|
11
|
+
|
12
|
+
class ActiveRecord::Base
|
13
|
+
cattr_accessor :master_mock, :slave_mock
|
14
|
+
def self.test_connection(config)
|
15
|
+
config[:database] == 'slave' ? slave_mock : master_mock
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe ActiveRecord::ConnectionAdapters::MasterSlaveAdapter do
|
20
|
+
let(:default_database_setup) do
|
21
|
+
{
|
22
|
+
:adapter => 'master_slave',
|
23
|
+
:username => 'root',
|
24
|
+
:database => 'slave',
|
25
|
+
:connection_adapter => 'test',
|
26
|
+
:master => { :username => 'root', :database => 'master' },
|
27
|
+
:slaves => [{ :database => 'slave' }],
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
let(:database_setup) { default_database_setup }
|
32
|
+
|
33
|
+
let(:mocked_methods) do
|
34
|
+
{
|
35
|
+
:reconnect! => true,
|
36
|
+
:disconnect! => true,
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
let!(:master_connection) do
|
41
|
+
mock(
|
42
|
+
'master connection',
|
43
|
+
mocked_methods.merge(:open_transactions => 0)
|
44
|
+
).tap do |conn|
|
45
|
+
conn.stub!(:uncached).and_yield
|
46
|
+
ActiveRecord::Base.master_mock = conn
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
let!(:slave_connection) do
|
51
|
+
mock('slave connection', mocked_methods).tap do |conn|
|
52
|
+
conn.stub!(:uncached).and_yield
|
53
|
+
ActiveRecord::Base.slave_mock = conn
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def adapter_connection
|
58
|
+
ActiveRecord::Base.connection
|
59
|
+
end
|
60
|
+
|
61
|
+
SchemaStatements = ActiveRecord::ConnectionAdapters::SchemaStatements.instance_methods.map(&:to_sym)
|
62
|
+
SelectMethods = [ :select_all, :select_one, :select_rows, :select_value, :select_values ]
|
63
|
+
Clock = ActiveRecord::ConnectionAdapters::MasterSlaveAdapter::Clock
|
64
|
+
|
65
|
+
before do
|
66
|
+
unless database_setup[:disable_connection_test] == 'true'
|
67
|
+
[ master_connection, slave_connection ].each do |c|
|
68
|
+
c.should_receive(:active?).exactly(2).times.and_return(true)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
ActiveRecord::Base.establish_connection(database_setup)
|
72
|
+
end
|
73
|
+
|
74
|
+
after do
|
75
|
+
ActiveRecord::Base.connection_handler.clear_all_connections!
|
76
|
+
end
|
77
|
+
|
78
|
+
describe 'common configuration' do
|
79
|
+
before do
|
80
|
+
[ master_connection, slave_connection ].each do |c|
|
81
|
+
c.stub!( :select_value ).with( "SELECT 1", "test select" ).and_return( true )
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should call 'columns' on master" do
|
86
|
+
master_connection.should_receive(:columns)
|
87
|
+
adapter_connection.columns
|
88
|
+
end
|
89
|
+
|
90
|
+
SelectMethods.each do |method|
|
91
|
+
it "should send the method '#{method}' to the slave connection" do
|
92
|
+
master_connection.stub!( :open_transactions ).and_return( 0 )
|
93
|
+
slave_connection.should_receive( method ).with('testing').and_return( true )
|
94
|
+
adapter_connection.send( method, 'testing' )
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should send the method '#{method}' to the master connection if with_master was specified" do
|
98
|
+
master_connection.should_receive( method ).with('testing').and_return( true )
|
99
|
+
ActiveRecord::Base.with_master do
|
100
|
+
adapter_connection.send( method, 'testing' )
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should send the method '#{method}' to the slave connection if with_slave was specified" do
|
105
|
+
slave_connection.should_receive( method ).with('testing').and_return( true )
|
106
|
+
ActiveRecord::Base.with_slave do
|
107
|
+
adapter_connection.send( method, 'testing' )
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should send the method '#{method}' to the master connection if there are open transactions" do
|
112
|
+
master_connection.stub!( :open_transactions ).and_return( 1 )
|
113
|
+
master_connection.should_receive( method ).with('testing').and_return( true )
|
114
|
+
ActiveRecord::Base.with_master do
|
115
|
+
adapter_connection.send( method, 'testing' )
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should send the method '#{method}' to the master connection if there are open transactions, even in with_slave" do
|
120
|
+
master_connection.stub!( :open_transactions ).and_return( 1 )
|
121
|
+
master_connection.should_receive( method ).with('testing').and_return( true )
|
122
|
+
ActiveRecord::Base.with_slave do
|
123
|
+
adapter_connection.send( method, 'testing' )
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end # /SelectMethods.each
|
127
|
+
|
128
|
+
SchemaStatements.each do |method|
|
129
|
+
it "should send the method '#{method}' from ActiveRecord::ConnectionAdapters::SchemaStatements to the master" do
|
130
|
+
master_connection.should_receive( method ).and_return( true )
|
131
|
+
adapter_connection.send( method )
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
(SchemaStatements - SelectMethods).each do |method|
|
136
|
+
it "should send the method '#{method}' from ActiveRecord::ConnectionAdapters::DatabaseStatements to the master" do
|
137
|
+
master_connection.should_receive( method ).and_return( true )
|
138
|
+
adapter_connection.send( method )
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should call #visitor on master connection" do
|
143
|
+
master_connection.should_receive(:visitor)
|
144
|
+
adapter_connection.visitor
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'should be a master slave connection' do
|
148
|
+
adapter_connection.class.should == ActiveRecord::ConnectionAdapters::MasterSlaveAdapter
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should have a master connection' do
|
152
|
+
adapter_connection.master_connection.should == master_connection
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'should have a slave connection' do
|
156
|
+
master_connection.stub!( :open_transactions ).and_return( 0 )
|
157
|
+
adapter_connection.slave_connection!.should == slave_connection
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe "connection testing" do
|
162
|
+
context "disabled" do
|
163
|
+
let(:database_setup) do
|
164
|
+
default_database_setup.merge(:disable_connection_test => 'true')
|
165
|
+
end
|
166
|
+
|
167
|
+
context "on master" do
|
168
|
+
SchemaStatements.each do |method|
|
169
|
+
it "should not perform the testing when #{method} is called" do
|
170
|
+
master_connection.tap do |c|
|
171
|
+
c.should_not_receive(:active?)
|
172
|
+
c.should_receive(method).with('testing').and_return(true)
|
173
|
+
end
|
174
|
+
adapter_connection.send(method, 'testing')
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
context "on slave" do
|
180
|
+
SelectMethods.each do |method|
|
181
|
+
it "should not perform the testing when #{method} is called" do
|
182
|
+
slave_connection.tap do |c|
|
183
|
+
c.should_not_receive(:active?)
|
184
|
+
c.should_receive(method).with('testing').and_return(true)
|
185
|
+
end
|
186
|
+
adapter_connection.send(method, 'testing')
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe 'with connection eager loading enabled' do
|
194
|
+
it 'should eager load the connections' do
|
195
|
+
adapter_connection.connections.should include(master_connection, slave_connection)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
describe 'consistency' do
|
200
|
+
before do
|
201
|
+
ActiveRecord::ConnectionAdapters::MasterSlaveAdapter.reset!
|
202
|
+
|
203
|
+
[ master_connection, slave_connection ].each do |c|
|
204
|
+
c.stub!(:select_value).with("SELECT 1", "test select").and_return(true)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def zero
|
209
|
+
Clock.zero
|
210
|
+
end
|
211
|
+
|
212
|
+
def master_position(pos)
|
213
|
+
Clock.new('', pos)
|
214
|
+
end
|
215
|
+
|
216
|
+
def slave_should_report_clock(pos)
|
217
|
+
pos = Array(pos)
|
218
|
+
values = pos.map { |p| { 'Relay_Master_Log_File' => '', 'Exec_Master_Log_Pos' => p } }
|
219
|
+
slave_connection.
|
220
|
+
should_receive('select_one').exactly(pos.length).with('SHOW SLAVE STATUS').
|
221
|
+
and_return(*values)
|
222
|
+
end
|
223
|
+
|
224
|
+
def master_should_report_clock(pos)
|
225
|
+
pos = Array(pos)
|
226
|
+
values = pos.map { |p| { 'File' => '', 'Position' => p } }
|
227
|
+
master_connection.
|
228
|
+
should_receive('select_one').exactly(pos.length).with('SHOW MASTER STATUS').
|
229
|
+
and_return(*values)
|
230
|
+
end
|
231
|
+
|
232
|
+
SelectMethods.each do |method|
|
233
|
+
it "should raise an exception if consistency is nil" do
|
234
|
+
lambda do
|
235
|
+
ActiveRecord::Base.with_consistency(nil) do
|
236
|
+
end
|
237
|
+
end.should raise_error(ArgumentError)
|
238
|
+
end
|
239
|
+
|
240
|
+
it "should send the method '#{method}' to the slave if clock.zero is given" do
|
241
|
+
slave_should_report_clock(0)
|
242
|
+
slave_connection.should_receive(method).with('testing').and_return(true)
|
243
|
+
old_clock = zero
|
244
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
245
|
+
adapter_connection.send(method, 'testing')
|
246
|
+
end
|
247
|
+
new_clock.should be_a(zero.class)
|
248
|
+
new_clock.should equal(zero)
|
249
|
+
end
|
250
|
+
|
251
|
+
it "should send the method '#{method}' to the master if slave hasn't cought up to required clock yet" do
|
252
|
+
slave_should_report_clock(0)
|
253
|
+
master_connection.should_receive(method).with('testing').and_return(true)
|
254
|
+
old_clock = master_position(1)
|
255
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
256
|
+
adapter_connection.send(method, 'testing' )
|
257
|
+
end
|
258
|
+
new_clock.should be_a(zero.class)
|
259
|
+
new_clock.should equal(old_clock)
|
260
|
+
end
|
261
|
+
|
262
|
+
it "should send the method '#{method}' to the master connection if there are open transactions" do
|
263
|
+
master_connection.stub!(:open_transactions).and_return(1)
|
264
|
+
master_connection.should_receive(method).with('testing').and_return(true)
|
265
|
+
old_clock = zero
|
266
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
267
|
+
adapter_connection.send(method, 'testing')
|
268
|
+
end
|
269
|
+
new_clock.should be_a(zero.class)
|
270
|
+
new_clock.should equal(zero)
|
271
|
+
end
|
272
|
+
|
273
|
+
it "should send the method '#{method}' to the master after a write operation" do
|
274
|
+
slave_should_report_clock(0)
|
275
|
+
master_should_report_clock(2)
|
276
|
+
slave_connection.should_receive(method).with('testing').and_return(true)
|
277
|
+
master_connection.should_receive('update').with('testing').and_return(true)
|
278
|
+
master_connection.should_receive(method).with('testing').and_return(true)
|
279
|
+
old_clock = zero
|
280
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
281
|
+
adapter_connection.send(method, 'testing') # slave
|
282
|
+
adapter_connection.send('update', 'testing') # master
|
283
|
+
adapter_connection.send(method, 'testing') # master
|
284
|
+
end
|
285
|
+
new_clock.should be_a(zero.class)
|
286
|
+
new_clock.should > old_clock
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
it "should update the clock after a transaction" do
|
291
|
+
slave_should_report_clock(0)
|
292
|
+
master_should_report_clock([0, 1, 1])
|
293
|
+
|
294
|
+
slave_connection.
|
295
|
+
should_receive('select_all').exactly(1).times.with('testing').
|
296
|
+
and_return(true)
|
297
|
+
|
298
|
+
master_connection.
|
299
|
+
should_receive('update').exactly(3).times.with('testing').
|
300
|
+
and_return(true)
|
301
|
+
master_connection.
|
302
|
+
should_receive('select_all').exactly(5).times.with('testing').
|
303
|
+
and_return(true)
|
304
|
+
%w(begin_db_transaction
|
305
|
+
commit_db_transaction
|
306
|
+
increment_open_transactions
|
307
|
+
decrement_open_transactions
|
308
|
+
outside_transaction?).each do |txstmt|
|
309
|
+
master_connection.should_receive(txstmt).exactly(1).times
|
310
|
+
end
|
311
|
+
|
312
|
+
master_connection.
|
313
|
+
should_receive('open_transactions').exactly(13).times.
|
314
|
+
and_return(
|
315
|
+
# adapter: with_consistency, select_all, update, select_all
|
316
|
+
0, 0, 0, 0,
|
317
|
+
# connection: transaction
|
318
|
+
0,
|
319
|
+
# adapter: select_all, update, select_all, commit_db_transaction
|
320
|
+
1, 1, 1, 0,
|
321
|
+
# connection: transaction (ensure)
|
322
|
+
0,
|
323
|
+
# adapter: select_all, update, select_all
|
324
|
+
0, 0, 0
|
325
|
+
)
|
326
|
+
|
327
|
+
old_clock = zero
|
328
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
329
|
+
adapter_connection.send('select_all', 'testing') # slave s=0 m=0
|
330
|
+
adapter_connection.send('update', 'testing') # master s=0 m=1
|
331
|
+
adapter_connection.send('select_all', 'testing') # master s=0 m=1
|
332
|
+
|
333
|
+
ActiveRecord::Base.transaction do
|
334
|
+
adapter_connection.send('select_all', 'testing') # master s=0 m=1
|
335
|
+
adapter_connection.send('update', 'testing') # master s=0 m=1
|
336
|
+
adapter_connection.send('select_all', 'testing') # master s=0 m=1
|
337
|
+
end
|
338
|
+
|
339
|
+
adapter_connection.send('select_all', 'testing') # master s=0 m=2
|
340
|
+
adapter_connection.send('update', 'testing') # master s=0 m=3
|
341
|
+
adapter_connection.send('select_all', 'testing') # master s=0 m=3
|
342
|
+
end
|
343
|
+
|
344
|
+
new_clock.should > old_clock
|
345
|
+
end
|
346
|
+
|
347
|
+
context "with nested with_consistency" do
|
348
|
+
it "should return the same clock if not writing and no lag" do
|
349
|
+
slave_should_report_clock(0) # note: tests memoizing slave clock
|
350
|
+
slave_connection.
|
351
|
+
should_receive('select_one').exactly(3).times.with('testing').
|
352
|
+
and_return(true)
|
353
|
+
|
354
|
+
old_clock = zero
|
355
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
356
|
+
adapter_connection.send('select_one', 'testing')
|
357
|
+
ActiveRecord::Base.with_consistency(old_clock) do
|
358
|
+
adapter_connection.send('select_one', 'testing')
|
359
|
+
end
|
360
|
+
adapter_connection.send('select_one', 'testing')
|
361
|
+
end
|
362
|
+
new_clock.should equal(old_clock)
|
363
|
+
end
|
364
|
+
|
365
|
+
it "requesting a newer clock should return a new clock" do
|
366
|
+
adapter_connection.
|
367
|
+
should_receive('slave_consistent?').exactly(2).times.
|
368
|
+
and_return(true, false)
|
369
|
+
slave_connection.
|
370
|
+
should_receive('select_all').exactly(2).times.with('testing').
|
371
|
+
and_return(true)
|
372
|
+
master_connection.
|
373
|
+
should_receive('select_all').exactly(1).times.with('testing').
|
374
|
+
and_return(true)
|
375
|
+
|
376
|
+
start_clock = zero
|
377
|
+
inner_clock = zero
|
378
|
+
outer_clock = ActiveRecord::Base.with_consistency(start_clock) do
|
379
|
+
adapter_connection.send('select_all', 'testing') # slave
|
380
|
+
inner_clock = ActiveRecord::Base.with_consistency(master_position(1)) do
|
381
|
+
adapter_connection.send('select_all', 'testing') # master
|
382
|
+
end
|
383
|
+
adapter_connection.send('select_all', 'testing') # slave
|
384
|
+
end
|
385
|
+
|
386
|
+
start_clock.should equal(outer_clock)
|
387
|
+
inner_clock.should > start_clock
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
it "should do the right thing when nested inside with_master" do
|
392
|
+
slave_should_report_clock(0)
|
393
|
+
slave_connection.should_receive('select_all').exactly(1).times.with('testing').and_return(true)
|
394
|
+
master_connection.should_receive('select_all').exactly(2).times.with('testing').and_return(true)
|
395
|
+
ActiveRecord::Base.with_master do
|
396
|
+
adapter_connection.send('select_all', 'testing') # master
|
397
|
+
ActiveRecord::Base.with_consistency(zero) do
|
398
|
+
adapter_connection.send('select_all', 'testing') # slave
|
399
|
+
end
|
400
|
+
adapter_connection.send('select_all', 'testing') # master
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
it "should do the right thing when nested inside with_slave" do
|
405
|
+
slave_should_report_clock(0)
|
406
|
+
slave_connection.should_receive('select_all').exactly(3).times.with('testing').and_return(true)
|
407
|
+
ActiveRecord::Base.with_slave do
|
408
|
+
adapter_connection.send('select_all', 'testing') # slave
|
409
|
+
ActiveRecord::Base.with_consistency(zero) do
|
410
|
+
adapter_connection.send('select_all', 'testing') # slave
|
411
|
+
end
|
412
|
+
adapter_connection.send('select_all', 'testing') # slave
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
it "should do the right thing when wrapping with_master" do
|
417
|
+
slave_should_report_clock(0)
|
418
|
+
slave_connection.should_receive('select_all').exactly(2).times.with('testing').and_return(true)
|
419
|
+
master_connection.should_receive('select_all').exactly(1).times.with('testing').and_return(true)
|
420
|
+
ActiveRecord::Base.with_consistency(zero) do
|
421
|
+
adapter_connection.send('select_all', 'testing') # slave
|
422
|
+
ActiveRecord::Base.with_master do
|
423
|
+
adapter_connection.send('select_all', 'testing') # master
|
424
|
+
end
|
425
|
+
adapter_connection.send('select_all', 'testing') # slave
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
it "should do the right thing when wrapping with_slave" do
|
430
|
+
slave_should_report_clock(0)
|
431
|
+
slave_connection.should_receive('select_all').exactly(1).times.with('testing').and_return(true)
|
432
|
+
master_connection.should_receive('select_all').exactly(2).times.with('testing').and_return(true)
|
433
|
+
ActiveRecord::Base.with_consistency(master_position(1)) do
|
434
|
+
adapter_connection.send('select_all', 'testing') # master
|
435
|
+
ActiveRecord::Base.with_slave do
|
436
|
+
adapter_connection.send('select_all', 'testing') # slave
|
437
|
+
end
|
438
|
+
adapter_connection.send('select_all', 'testing') # master
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end # /with_consistency
|
442
|
+
|
443
|
+
describe "transaction callbacks" do
|
444
|
+
before do
|
445
|
+
ActiveRecord::ConnectionAdapters::MasterSlaveAdapter.reset!
|
446
|
+
end
|
447
|
+
|
448
|
+
def run_tx
|
449
|
+
adapter_connection.
|
450
|
+
should_receive('master_clock').
|
451
|
+
and_return(Clock.new('', 1))
|
452
|
+
%w(begin_db_transaction
|
453
|
+
commit_db_transaction
|
454
|
+
increment_open_transactions
|
455
|
+
decrement_open_transactions
|
456
|
+
outside_transaction?).each do |txstmt|
|
457
|
+
master_connection.
|
458
|
+
should_receive(txstmt).exactly(1).times
|
459
|
+
end
|
460
|
+
master_connection.
|
461
|
+
should_receive('open_transactions').exactly(4).times.
|
462
|
+
and_return(0, 1, 0, 0)
|
463
|
+
|
464
|
+
master_connection.
|
465
|
+
should_receive('update').with('testing').
|
466
|
+
and_return(true)
|
467
|
+
|
468
|
+
ActiveRecord::Base.transaction do
|
469
|
+
adapter_connection.send('update', 'testing')
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
def fail_tx
|
474
|
+
%w(begin_db_transaction
|
475
|
+
rollback_db_transaction
|
476
|
+
increment_open_transactions
|
477
|
+
decrement_open_transactions).each do |txstmt|
|
478
|
+
master_connection.
|
479
|
+
should_receive(txstmt).exactly(1).times
|
480
|
+
end
|
481
|
+
master_connection.
|
482
|
+
should_receive('outside_transaction?').exactly(2).times
|
483
|
+
master_connection.
|
484
|
+
should_receive('open_transactions').exactly(3).times.
|
485
|
+
and_return(0, 1, 0)
|
486
|
+
master_connection.
|
487
|
+
should_receive('update').with('testing').
|
488
|
+
and_return(true)
|
489
|
+
|
490
|
+
ActiveRecord::Base.transaction do
|
491
|
+
adapter_connection.send('update', 'testing')
|
492
|
+
raise "rollback"
|
493
|
+
end
|
494
|
+
rescue
|
495
|
+
nil
|
496
|
+
end
|
497
|
+
|
498
|
+
context "on commit" do
|
499
|
+
it "on_commit callback should be called" do
|
500
|
+
x = false
|
501
|
+
adapter_connection.on_commit { x = true }
|
502
|
+
lambda { run_tx }.should change { x }.to(true)
|
503
|
+
end
|
504
|
+
|
505
|
+
it "on_rollback callback should not be called" do
|
506
|
+
x = false
|
507
|
+
adapter_connection.on_rollback { x = true }
|
508
|
+
lambda { run_tx }.should_not change { x }
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
context "on rollback" do
|
513
|
+
it "on_commit callback should not be called" do
|
514
|
+
x = false
|
515
|
+
adapter_connection.on_commit { x = true }
|
516
|
+
lambda { fail_tx }.should_not change { x }
|
517
|
+
end
|
518
|
+
|
519
|
+
it "on_rollback callback should be called" do
|
520
|
+
x = false
|
521
|
+
adapter_connection.on_rollback { x = true }
|
522
|
+
lambda { fail_tx }.should change { x }.to(true)
|
523
|
+
end
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
describe "query cache" do
|
528
|
+
describe "#cache" do
|
529
|
+
it "activities query caching on all connections" do
|
530
|
+
master_connection.should_receive(:cache).and_yield
|
531
|
+
slave_connection.should_receive(:cache).and_yield
|
532
|
+
master_connection.should_not_receive(:select_value)
|
533
|
+
slave_connection.should_receive(:select_value)
|
534
|
+
|
535
|
+
adapter_connection.cache do
|
536
|
+
adapter_connection.select_value("SELECT 42")
|
537
|
+
end
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
541
|
+
describe "#uncached" do
|
542
|
+
it "deactivates query caching on all connections" do
|
543
|
+
master_connection.should_receive(:uncached).and_yield
|
544
|
+
slave_connection.should_receive(:uncached).and_yield
|
545
|
+
master_connection.should_not_receive(:select_value)
|
546
|
+
slave_connection.should_receive(:select_value)
|
547
|
+
|
548
|
+
adapter_connection.uncached do
|
549
|
+
adapter_connection.select_value("SELECT 42")
|
550
|
+
end
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
describe "#clear_query_cache" do
|
555
|
+
it "clears the query cache on all connections" do
|
556
|
+
master_connection.should_receive(:clear_query_cache)
|
557
|
+
slave_connection.should_receive(:clear_query_cache)
|
558
|
+
|
559
|
+
adapter_connection.clear_query_cache
|
560
|
+
end
|
561
|
+
end
|
562
|
+
end
|
563
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: master_slave_adapter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mauricio Linhares
|
9
|
+
- Torsten Curdt
|
10
|
+
- Kim Altintop
|
11
|
+
- Omid Aladini
|
12
|
+
- SoundCloud
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
date: 2012-05-21 00:00:00.000000000 Z
|
17
|
+
dependencies:
|
18
|
+
- !ruby/object:Gem::Dependency
|
19
|
+
name: rspec
|
20
|
+
requirement: !ruby/object:Gem::Requirement
|
21
|
+
none: false
|
22
|
+
requirements:
|
23
|
+
- - ! '>='
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
type: :development
|
27
|
+
prerelease: false
|
28
|
+
version_requirements: !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rake
|
36
|
+
requirement: !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ! '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
type: :development
|
43
|
+
prerelease: false
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: activerecord
|
52
|
+
requirement: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ~>
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 2.3.9
|
58
|
+
type: :runtime
|
59
|
+
prerelease: false
|
60
|
+
version_requirements: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 2.3.9
|
66
|
+
description: (MySQL) Replication Aware Master/Slave Database Adapter for Rails/ActiveRecord
|
67
|
+
email: kim@soundcloud.com tcurdt@soundcloud.com omid@soundcloud.com
|
68
|
+
executables: []
|
69
|
+
extensions: []
|
70
|
+
extra_rdoc_files: []
|
71
|
+
files:
|
72
|
+
- .gitignore
|
73
|
+
- .travis.yml
|
74
|
+
- CHANGELOG.md
|
75
|
+
- Gemfile
|
76
|
+
- LICENSE
|
77
|
+
- Rakefile
|
78
|
+
- Readme.md
|
79
|
+
- TODO.txt
|
80
|
+
- VERSION
|
81
|
+
- lib/active_record/connection_adapters/master_slave_adapter.rb
|
82
|
+
- lib/active_record/connection_adapters/mysql_master_slave_adapter.rb
|
83
|
+
- lib/master_slave_adapter.rb
|
84
|
+
- master_slave_adapter.gemspec
|
85
|
+
- spec/master_slave_adapter_spec.rb
|
86
|
+
homepage: http://github.com/soundcloud/master_slave_adapter
|
87
|
+
licenses: []
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: 1.9.2
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.3.7
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 1.8.21
|
107
|
+
signing_key:
|
108
|
+
specification_version: 3
|
109
|
+
summary: Replication Aware Master/Slave Database Adapter for Rails/ActiveRecord
|
110
|
+
test_files:
|
111
|
+
- spec/master_slave_adapter_spec.rb
|