master_slave_adapter 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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