octoball 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe Octoball do
4
+ describe 'method dispatch' do
5
+ before :each do
6
+ @client = Client.using(:canada).create!
7
+ @client.items << Item.using(:canada).create!
8
+ @client.reload
9
+ end
10
+
11
+ it 'computes the size of the collection without loading it' do
12
+ expect(@client.items.size).to eq(1)
13
+
14
+ expect(@client.items.loaded?).to be false
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Octoball::LogSubscriber, :shards => [:canada] do
4
+ before :each do
5
+ @out = StringIO.new
6
+ @log = Logger.new(@out)
7
+ ActiveRecord::Base.logger = @log
8
+ ActiveRecord::Base.logger.level = Logger::DEBUG
9
+ end
10
+
11
+ after :each do
12
+ ActiveRecord::Base.logger = Logger.new(File.open('database.log', 'a'))
13
+ end
14
+
15
+ it 'should add to the default logger the shard name the query was sent to' do
16
+ User.using(:canada).create!(:name => 'test')
17
+ expect(@out.string).to match(/Shard: canada/)
18
+ end
19
+ end
@@ -0,0 +1,688 @@
1
+ require 'spec_helper'
2
+
3
+ describe Octoball do
4
+ describe '#using method' do
5
+ it 'should allow to send a block to the master shard' do
6
+ Octoball.using(:master) do
7
+ User.create!(:name => 'Block test')
8
+ end
9
+
10
+ expect(User.using(:master).find_by_name('Block test')).not_to be_nil
11
+ end
12
+
13
+ it 'should allow to pass a string as the shard name to a AR subclass' do
14
+ User.using('canada').create!(:name => 'Rafael Pilha')
15
+
16
+ expect(User.using('canada').find_by_name('Rafael Pilha')).not_to be_nil
17
+ end
18
+
19
+ it 'should allow comparison of a string shard name with symbol shard name' do
20
+ u = User.using('canada').create!(:name => 'Rafael Pilha')
21
+ expect(u).to eq(User.using(:canada).find_by_name('Rafael Pilha'))
22
+ end
23
+
24
+ it 'should allow comparison of a symbol shard name with string shard name' do
25
+ u = User.using(:canada).create!(:name => 'Rafael Pilha')
26
+ expect(u).to eq(User.using('canada').find_by_name('Rafael Pilha'))
27
+ end
28
+
29
+ it 'should allow to pass a string as the shard name to a block' do
30
+ Octoball.using('canada') do
31
+ User.create!(:name => 'Rafael Pilha')
32
+ end
33
+
34
+ expect(User.using('canada').find_by_name('Rafael Pilha')).not_to be_nil
35
+ end
36
+
37
+ it 'should allow selecting the shards on scope' do
38
+ User.using(:canada).create!(:name => 'oi')
39
+ expect(User.using(:canada).count).to eq(1)
40
+ expect(User.count).to eq(0)
41
+ end
42
+
43
+ it 'should allow selecting the shard using #new' do
44
+ u = User.using(:canada).new
45
+ u.name = 'Thiago'
46
+ u.save
47
+
48
+ expect(User.using(:canada).count).to eq(1)
49
+ expect(User.using(:brazil).count).to eq(0)
50
+
51
+ u1 = User.new
52
+ u1.name = 'Joaquim'
53
+ u2 = User.using(:canada).new
54
+ u2.name = 'Manuel'
55
+ u1.save
56
+ u2.save
57
+
58
+ expect(User.using(:canada).all).to eq([u, u2])
59
+ expect(User.all).to eq([u1])
60
+ end
61
+
62
+ it "should allow the #select method to fetch the correct data when using a block" do
63
+ canadian_user = User.using(:canada).create!(:name => 'Rafael Pilha')
64
+
65
+ Octoball.using('canada') do
66
+ @all_canadian_user_ids = User.select('id').to_a
67
+ end
68
+
69
+ expect(@all_canadian_user_ids).to eq([canadian_user])
70
+ end
71
+
72
+ it "should allow objects to be fetch using different blocks - GH #306" do
73
+ canadian_user = User.using(:canada).create!(:name => 'Rafael Pilha')
74
+
75
+ Octoball.using(:canada) { @users = User.where('id is not null') }
76
+ Octoball.using(:canada) { @user = @users.first }
77
+
78
+ Octoball.using(:canada) { @user2 = User.where('id is not null').first }
79
+
80
+ expect(@user).to eq(canadian_user)
81
+ expect(@user2).to eq(canadian_user)
82
+ end
83
+
84
+ describe 'multiple calls to the same scope' do
85
+ it 'works with nil response' do
86
+ scope = User.using(:canada)
87
+ expect(scope.count).to eq(0)
88
+ expect(scope.first).to be_nil
89
+ end
90
+
91
+ it 'works with non-nil response' do
92
+ user = User.using(:canada).create!(:name => 'oi')
93
+ scope = User.using(:canada)
94
+ expect(scope.count).to eq(1)
95
+ expect(scope.first).to eq(user)
96
+ end
97
+ end
98
+
99
+ it 'should select the correct shard' do
100
+ User.using(:canada)
101
+ User.create!(:name => 'oi')
102
+ expect(User.count).to eq(1)
103
+ end
104
+
105
+ it 'should ensure that the connection will be cleaned' do
106
+ expect(ActiveRecord::Base.connection.current_shard).to eq(:default)
107
+ expect do
108
+ Octoball.using(:canada) do
109
+ fail 'Some Exception'
110
+ end
111
+ end.to raise_error(RuntimeError)
112
+
113
+ expect(ActiveRecord::Base.connection.current_shard).to eq(:default)
114
+ end
115
+
116
+ it 'should allow creating more than one user' do
117
+ User.using(:canada).create([{ :name => 'America User 1' }, { :name => 'America User 2' }])
118
+ User.create!(:name => 'Thiago')
119
+ expect(User.using(:canada).find_by_name('America User 1')).not_to be_nil
120
+ expect(User.using(:canada).find_by_name('America User 2')).not_to be_nil
121
+ expect(User.using(:master).find_by_name('Thiago')).not_to be_nil
122
+ end
123
+
124
+ it 'should clean #current_shard from proxy when using execute' do
125
+ User.using(:canada).connection.execute('select * from users limit 1;')
126
+ expect(User.connection.current_shard).to eq(:default)
127
+ end
128
+
129
+ it 'should allow scoping dynamically' do
130
+ User.using(:canada).using(:master).using(:canada).create!(:name => 'oi')
131
+ expect(User.using(:canada).using(:master).count).to eq(0)
132
+ expect(User.using(:master).using(:canada).count).to eq(1)
133
+ end
134
+
135
+ it 'should allow find inside blocks' do
136
+ @user = User.using(:brazil).create!(:name => 'Thiago')
137
+
138
+ Octoball.using(:brazil) do
139
+ expect(User.first).to eq(@user)
140
+ end
141
+
142
+ expect(User.using(:brazil).find_by_name('Thiago')).to eq(@user)
143
+ end
144
+
145
+ it 'should clean the current_shard after executing the current query' do
146
+ User.using(:canada).create!(:name => 'oi')
147
+ expect(User.count).to eq(0)
148
+ end
149
+
150
+ it 'should support both groups and alone shards' do
151
+ _u = User.using(:alone_shard).create!(:name => 'Alone')
152
+ expect(User.using(:alone_shard).count).to eq(1)
153
+ expect(User.using(:canada).count).to eq(0)
154
+ expect(User.using(:brazil).count).to eq(0)
155
+ expect(User.count).to eq(0)
156
+ end
157
+
158
+ it 'should work with named scopes' do
159
+ u = User.using(:brazil).create!(:name => 'Thiago')
160
+
161
+ expect(User.thiago.using(:brazil).first).to eq(u)
162
+ expect(User.using(:brazil).thiago.first).to eq(u)
163
+
164
+ Octoball.using(:brazil) do
165
+ expect(User.thiago.first).to eq(u)
166
+ end
167
+ end
168
+
169
+ describe '#current_shard attribute' do
170
+ it 'should store the attribute when you create or find an object' do
171
+ u = User.using(:alone_shard).create!(:name => 'Alone')
172
+ expect(u.current_shard).to eq(:alone_shard)
173
+ User.using(:canada).create!(:name => 'oi')
174
+ u = User.using(:canada).find_by_name('oi')
175
+ expect(u.current_shard).to eq(:canada)
176
+ end
177
+
178
+ it 'should store the attribute when you find multiple instances' do
179
+ 5.times { User.using(:alone_shard).create!(:name => 'Alone') }
180
+
181
+ User.using(:alone_shard).all.each do |u|
182
+ expect(u.current_shard).to eq(:alone_shard)
183
+ end
184
+ end
185
+
186
+ it 'should works when you find, and after that, alter that object' do
187
+ alone_user = User.using(:alone_shard).create!(:name => 'Alone')
188
+ _mstr_user = User.using(:master).create!(:name => 'Master')
189
+ alone_user.name = 'teste'
190
+ alone_user.save
191
+ expect(User.using(:master).first.name).to eq('Master')
192
+ expect(User.using(:alone_shard).first.name).to eq('teste')
193
+ end
194
+
195
+ it 'should work for the reload method' do
196
+ User.using(:alone_shard).create!(:name => 'Alone')
197
+ u = User.using(:alone_shard).find_by_name('Alone')
198
+ u.reload
199
+ expect(u.name).to eq('Alone')
200
+ end
201
+
202
+ it 'should work passing some arguments to reload method' do
203
+ User.using(:alone_shard).create!(:name => 'Alone')
204
+ u = User.using(:alone_shard).find_by_name('Alone')
205
+ u.reload(:lock => true)
206
+ expect(u.name).to eq('Alone')
207
+ end
208
+ end
209
+
210
+ describe 'passing a block' do
211
+ it 'should allow queries be executed inside the block, ponting to a specific shard' do
212
+ Octoball.using(:canada) do
213
+ User.create(:name => 'oi')
214
+ end
215
+
216
+ expect(User.using(:canada).count).to eq(1)
217
+ expect(User.using(:master).count).to eq(0)
218
+ expect(User.count).to eq(0)
219
+ end
220
+
221
+ it 'should allow execute queries inside a model' do
222
+ u = User.new
223
+ u.awesome_queries
224
+ expect(User.using(:canada).count).to eq(1)
225
+ expect(User.count).to eq(0)
226
+ end
227
+ end
228
+
229
+ describe 'raising errors' do
230
+ it "should raise a error when you specify a shard that doesn't exist" do
231
+ expect { User.using(:crazy_shard).create!(:name => 'Thiago') }.to raise_error(ActiveRecord::ConnectionNotEstablished)
232
+ end
233
+ end
234
+
235
+ describe 'equality' do
236
+ let(:canada1) do
237
+ User.using(:canada).new(id: 1)
238
+ end
239
+
240
+ let(:canada1_dup) do
241
+ User.using(:canada).new(id: 1)
242
+ end
243
+
244
+ let(:brazil1) do
245
+ User.using(:brazil).new(id: 1)
246
+ end
247
+
248
+ it 'should work with persisted objects' do
249
+ u = User.using(:brazil).create(:name => 'Mike')
250
+ expect(User.using(:brazil).find_by_name('Mike')).to eq(u)
251
+ end
252
+
253
+ it 'should check current_shard when determining equality' do
254
+ expect(canada1).not_to eq(brazil1)
255
+ expect(canada1).to eq(canada1_dup)
256
+ end
257
+
258
+ it 'delegates equality check on scopes' do
259
+ u = User.using(:brazil).create!(:name => 'Mike')
260
+ expect(User.using(:brazil).where(:name => 'Mike')).to eq([u])
261
+ end
262
+ end
263
+ end
264
+
265
+ describe 'AR basic methods' do
266
+ it 'connects_to' do
267
+ expect(CustomConnection.connection.current_database).to eq('octoball_shard_2')
268
+ end
269
+
270
+ it 'reuses parent model connection' do
271
+ klass = Class.new(CustomConnection)
272
+
273
+ expect(klass.connection).to be klass.connection
274
+ end
275
+
276
+ it 'should not mess with custom connection table names' do
277
+ expect(Advert.connection.current_database).to eq('octoball_shard_1')
278
+ Advert.create!(:name => 'Teste')
279
+ end
280
+
281
+ it 'increment' do
282
+ _ = User.using(:brazil).create!(:name => 'Teste', :number => 10)
283
+ u = User.using(:brazil).find_by_number(10)
284
+ u.increment(:number)
285
+ u.save
286
+ expect(User.using(:brazil).find_by_number(11)).not_to be_nil
287
+ end
288
+
289
+ it 'increment!' do
290
+ _ = User.using(:brazil).create!(:name => 'Teste', :number => 10)
291
+ u = User.using(:brazil).find_by_number(10)
292
+ u.increment!(:number)
293
+ expect(User.using(:brazil).find_by_number(11)).not_to be_nil
294
+ end
295
+
296
+ it 'decrement' do
297
+ _ = User.using(:brazil).create!(:name => 'Teste', :number => 10)
298
+ u = User.using(:brazil).find_by_number(10)
299
+ u.decrement(:number)
300
+ u.save
301
+ expect(User.using(:brazil).find_by_number(9)).not_to be_nil
302
+ end
303
+
304
+ it 'decrement!' do
305
+ _ = User.using(:brazil).create!(:name => 'Teste', :number => 10)
306
+ u = User.using(:brazil).find_by_number(10)
307
+ u.decrement!(:number)
308
+ expect(User.using(:brazil).find_by_number(9)).not_to be_nil
309
+ end
310
+
311
+ it 'toggle' do
312
+ _ = User.using(:brazil).create!(:name => 'Teste', :admin => false)
313
+ u = User.using(:brazil).find_by_name('Teste')
314
+ u.toggle(:admin)
315
+ u.save
316
+ expect(User.using(:brazil).find_by_name('Teste').admin).to be true
317
+ end
318
+
319
+ it 'toggle!' do
320
+ _ = User.using(:brazil).create!(:name => 'Teste', :admin => false)
321
+ u = User.using(:brazil).find_by_name('Teste')
322
+ u.toggle!(:admin)
323
+ expect(User.using(:brazil).find_by_name('Teste').admin).to be true
324
+ end
325
+
326
+ it 'count' do
327
+ _u = User.using(:brazil).create!(:name => 'User1')
328
+ _v = User.using(:brazil).create!(:name => 'User2')
329
+ _w = User.using(:brazil).create!(:name => 'User3')
330
+ expect(User.using(:brazil).where(:name => 'User2').all.count).to eq(1)
331
+ end
332
+
333
+ it 'maximum' do
334
+ _u = User.using(:brazil).create!(:name => 'Teste', :number => 11)
335
+ _v = User.using(:master).create!(:name => 'Teste', :number => 12)
336
+
337
+ expect(User.using(:brazil).maximum(:number)).to eq(11)
338
+ expect(User.using(:master).maximum(:number)).to eq(12)
339
+ end
340
+
341
+ it 'sum' do
342
+ u = User.using(:brazil).create!(:name => 'Teste', :number => 11)
343
+ v = User.using(:master).create!(:name => 'Teste', :number => 12)
344
+
345
+ expect(User.using(:master).sum(:number)).to eq(12)
346
+ expect(User.using(:brazil).sum(:number)).to eq(11)
347
+
348
+ expect(User.where(id: v.id).sum(:number)).to eq(12)
349
+ expect(User.using(:brazil).where(id: u.id).sum(:number)).to eq(11)
350
+ expect(User.using(:master).where(id: v.id).sum(:number)).to eq(12)
351
+ end
352
+
353
+ describe 'any?' do
354
+ before { User.using(:brazil).create!(:name => 'User1') }
355
+
356
+ it 'works when true' do
357
+ scope = User.using(:brazil).where(:name => 'User1')
358
+ expect(scope.any?).to be true
359
+ end
360
+
361
+ it 'works when false' do
362
+ scope = User.using(:brazil).where(:name => 'User2')
363
+ expect(scope.any?).to be false
364
+ end
365
+ end
366
+
367
+ it 'exists?' do
368
+ @user = User.using(:brazil).create!(:name => 'User1')
369
+
370
+ expect(User.using(:brazil).where(:name => 'User1').exists?).to be true
371
+ expect(User.using(:brazil).where(:name => 'User2').exists?).to be false
372
+ end
373
+
374
+ describe 'touch' do
375
+ it 'updates updated_at by default' do
376
+ @user = User.using(:brazil).create!(:name => 'User1')
377
+ User.using(:brazil).where(:id => @user.id).update_all(:updated_at => Time.now - 3.months)
378
+ @user.touch
379
+ expect(@user.reload.updated_at.in_time_zone('GMT').to_date).to eq(Time.now.in_time_zone('GMT').to_date)
380
+ end
381
+
382
+ it 'updates passed in attribute name' do
383
+ @user = User.using(:brazil).create!(:name => 'User1')
384
+ User.using(:brazil).where(:id => @user.id).update_all(:created_at => Time.now - 3.months)
385
+ @user.touch(:created_at)
386
+ expect(@user.reload.created_at.in_time_zone('GMT').to_date).to eq(Time.now.in_time_zone('GMT').to_date)
387
+ end
388
+ end
389
+
390
+ describe '#pluck' do
391
+ before { User.using(:brazil).create!(:name => 'User1') }
392
+
393
+ it 'should works from scope proxy' do
394
+ names = User.using(:brazil).pluck(:name)
395
+ expect(names).to eq(['User1'])
396
+ expect(User.using(:master).pluck(:name)).to eq([])
397
+ end
398
+ end
399
+
400
+ it 'update_column' do
401
+ @user = User.using(:brazil).create!(:name => 'User1')
402
+ @user2 = User.using(:brazil).find(@user.id)
403
+ @user2.update_column(:name, 'Joaquim Shard Brazil')
404
+ expect(User.using(:brazil).find_by_name('Joaquim Shard Brazil')).not_to be_nil
405
+ end
406
+
407
+ it 'update' do
408
+ @user = User.using(:brazil).create!(:name => 'User1')
409
+ @user2 = User.using(:brazil).find(@user.id)
410
+ @user2.update(:name => 'Joaquim')
411
+ expect(User.using(:brazil).find_by_name('Joaquim')).not_to be_nil
412
+ end
413
+
414
+ it 'using update inside a block' do
415
+ Octoball.using(:brazil) do
416
+ @user = User.create!(:name => 'User1')
417
+ @user2 = User.find(@user.id)
418
+ @user2.update(:name => 'Joaquim')
419
+ end
420
+
421
+ expect(User.find_by_name('Joaquim')).to be_nil
422
+ expect(User.using(:brazil).find_by_name('Joaquim')).not_to be_nil
423
+ end
424
+
425
+ it 'update' do
426
+ @user = User.using(:brazil).create!(:name => 'User1')
427
+ @user2 = User.using(:brazil).find(@user.id)
428
+ @user2.update(:name => 'Joaquim')
429
+ expect(User.using(:brazil).find_by_name('Joaquim')).not_to be_nil
430
+ end
431
+
432
+ it 'as_json' do
433
+ ActiveRecord::Base.include_root_in_json = false
434
+
435
+ Octoball.using(:brazil) do
436
+ User.create!(:name => 'User1')
437
+ end
438
+
439
+ user = User.using(:brazil).where(:name => 'User1').first
440
+ expect(user.as_json(:except => [:created_at, :updated_at, :id])).to eq('admin' => nil, 'name' => 'User1', 'number' => nil)
441
+ end
442
+
443
+ describe 'transaction' do
444
+ context 'without assigning a database' do
445
+ it 'works as expected' do
446
+ _u = User.create!(:name => 'Thiago')
447
+
448
+ expect(User.using(:brazil).count).to eq(0)
449
+ expect(User.using(:master).count).to eq(1)
450
+
451
+ User.using(:brazil).transaction do
452
+ expect(User.find_by_name('Thiago')).to be_nil
453
+ User.create!(:name => 'Brazil')
454
+ end
455
+
456
+ expect(User.using(:brazil).count).to eq(1)
457
+ expect(User.using(:master).count).to eq(1)
458
+ end
459
+ end
460
+
461
+ context 'when assigning a database' do
462
+ it 'works as expected' do
463
+ klass = User.using(:brazil)
464
+
465
+ klass.transaction do
466
+ klass.create!(:name => 'Brazil')
467
+ end
468
+
469
+ expect(klass.find_by_name('Brazil')).to be_present
470
+ end
471
+ end
472
+ end
473
+
474
+ describe "#finder methods" do
475
+ before(:each) do
476
+ @user1 = User.using(:brazil).create!(:name => 'User1')
477
+ @user2 = User.using(:brazil).create!(:name => 'User2')
478
+ @user3 = User.using(:brazil).create!(:name => 'User3')
479
+ end
480
+
481
+ it "#find_each should work with a block" do
482
+ result_array = []
483
+
484
+ User.using(:brazil).where("name is not NULL").find_each do |user|
485
+ result_array << user
486
+ end
487
+
488
+ expect(result_array).to eq([@user1, @user2, @user3])
489
+ end
490
+
491
+ it "#find_each should work with a where.not(...)" do
492
+ result_array = []
493
+
494
+ User.using(:brazil).where.not(:name => 'User2').find_each do |user|
495
+ result_array << user
496
+ end
497
+
498
+ expect(result_array).to eq([@user1, @user3])
499
+ end
500
+
501
+ it "#find_each should work as an enumerator" do
502
+ result_array = []
503
+
504
+ User.using(:brazil).where("name is not NULL").find_each.each do |user|
505
+ result_array << user
506
+ end
507
+
508
+ expect(result_array).to eq([@user1, @user2, @user3])
509
+ end
510
+
511
+ it "#find_each should work as a lazy enumerator" do
512
+ result_array = []
513
+
514
+ User.using(:brazil).where("name is not NULL").find_each.lazy.each do |user|
515
+ result_array << user
516
+ end
517
+
518
+ expect(result_array).to eq([@user1, @user2, @user3])
519
+ end
520
+
521
+ it "#find_in_batches should work with a block" do
522
+ result_array = []
523
+
524
+ User.using(:brazil).where("name is not NULL").find_in_batches(batch_size: 1) do |user|
525
+ result_array << user
526
+ end
527
+
528
+ expect(result_array).to eq([[@user1], [@user2], [@user3]])
529
+ end
530
+
531
+ it "#find_in_batches should work as an enumerator" do
532
+ result_array = []
533
+
534
+ User.using(:brazil).where("name is not NULL").find_in_batches(batch_size: 1).each do |user|
535
+ result_array << user
536
+ end
537
+
538
+ expect(result_array).to eq([[@user1], [@user2], [@user3]])
539
+ end
540
+
541
+ it "#find_in_batches should work as a lazy enumerator" do
542
+ result_array = []
543
+
544
+ User.using(:brazil).where("name is not NULL").find_in_batches(batch_size: 1).lazy.each do |user|
545
+ result_array << user
546
+ end
547
+
548
+ expect(result_array).to eq([[@user1], [@user2], [@user3]])
549
+ end
550
+ end
551
+
552
+ describe 'deleting a record' do
553
+ before(:each) do
554
+ @user = User.using(:brazil).create!(:name => 'User1')
555
+ @user2 = User.using(:brazil).find(@user.id)
556
+ end
557
+
558
+ it 'delete' do
559
+ @user2.delete
560
+ expect { User.using(:brazil).find(@user2.id) }.to raise_error(ActiveRecord::RecordNotFound)
561
+ end
562
+
563
+ it "delete within block shouldn't lose shard" do
564
+ Octoball.using(:brazil) do
565
+ @user2.delete
566
+ @user3 = User.create(:name => 'User3')
567
+
568
+ expect(User.connection.current_shard).to eq(:brazil)
569
+ expect(User.find(@user3.id)).to eq(@user3)
570
+ end
571
+ end
572
+
573
+ it 'destroy' do
574
+ @user2.destroy
575
+ expect { User.using(:brazil).find(@user2.id) }.to raise_error(ActiveRecord::RecordNotFound)
576
+ end
577
+
578
+ it "destroy within block shouldn't lose shard" do
579
+ Octoball.using(:brazil) do
580
+ @user2.destroy
581
+ @user3 = User.create(:name => 'User3')
582
+
583
+ expect(User.connection.current_shard).to eq(:brazil)
584
+ expect(User.find(@user3.id)).to eq(@user3)
585
+ end
586
+ end
587
+ end
588
+ end
589
+
590
+ describe 'custom connection' do
591
+ context 'by default' do
592
+ it 'with plain call should use custom connection' do
593
+ expect(CustomConnection.connection.current_database).to eq('octoball_shard_2')
594
+ end
595
+
596
+ it 'should use model-specific shard' do
597
+ expect(CustomConnection.using(:custom_shard).connection.current_database).to eq('octoball_shard_3')
598
+ end
599
+
600
+ it 'should use model-specific shard by Octoball.using block' do
601
+ Octoball.using(:custom_shard) do
602
+ expect(CustomConnection.connection.current_database).to eq('octoball_shard_3')
603
+ end
604
+ end
605
+
606
+ it 'should save to correct shard' do
607
+ expect { CustomConnection.create(:value => 'custom value') }.to change {
608
+ CustomConnection
609
+ .connection
610
+ .execute("select count(*) as ct from custom where value = 'custom value'")
611
+ .to_a.first.first
612
+ }.by 1
613
+ end
614
+ end
615
+
616
+ describe 'clear_active_connections!' do
617
+ it 'should not leak connection' do
618
+ CustomConnection.create(:value => 'custom value')
619
+
620
+ # This is what Rails, Sidekiq etc call--this normally handles all connection pools in the app
621
+ expect { ActiveRecord::Base.clear_active_connections! }
622
+ .to change { CustomConnection.connection_pool.active_connection? }
623
+
624
+ expect(CustomConnection.connection_pool.active_connection?).to be_falsey
625
+ end
626
+ end
627
+ end
628
+
629
+ describe 'when using set_table_name' do
630
+ it 'should work correctly' do
631
+ Bacon.using(:brazil).create!(:name => 'YUMMMYYYY')
632
+ end
633
+
634
+ it 'should work correctly with a block' do
635
+ Cheese.using(:brazil).create!(:name => 'YUMMMYYYY')
636
+ end
637
+ end
638
+
639
+ describe 'when using table_name=' do
640
+ it 'should work correctly' do
641
+ Ham.using(:brazil).create!(:name => 'YUMMMYYYY')
642
+ end
643
+ end
644
+
645
+ describe 'when you have joins/include' do
646
+ before(:each) do
647
+ @client1 = Client.using(:brazil).create(:name => 'Thiago')
648
+
649
+ Octoball.using(:canada) do
650
+ @client2 = Client.create(:name => 'Mike')
651
+ @client3 = Client.create(:name => 'Joao')
652
+ @item1 = Item.create(:client => @client2, :name => 'Item 1')
653
+ @item2 = Item.create(:client => @client2, :name => 'Item 2')
654
+ @item3 = Item.create(:client => @client3, :name => 'Item 3')
655
+ @part1 = Part.create(:item => @item1, :name => 'Part 1')
656
+ @part2 = Part.create(:item => @item1, :name => 'Part 2')
657
+ @part3 = Part.create(:item => @item2, :name => 'Part 3')
658
+ end
659
+
660
+ @item4 = Item.using(:brazil).create(:client => @client1, :name => 'Item 4')
661
+ end
662
+
663
+ it 'should work using the rails 3.x syntax' do
664
+ items = Item.using(:canada).joins(:client).where("clients.id = #{@client2.id}").all
665
+ expect(items).to eq([@item1, @item2])
666
+ end
667
+
668
+ it 'should work for include also, rails 3.x syntax' do
669
+ items = Item.using(:canada).includes(:client).where(:clients => { :id => @client2.id }).all
670
+ expect(items).to eq([@item1, @item2])
671
+ end
672
+ end
673
+
674
+ describe 'ActiveRecord::Base Validations' do
675
+ it 'should work correctly when using validations' do
676
+ @key = Keyboard.create!(:name => 'Key')
677
+ expect { Keyboard.using(:brazil).create!(:name => 'Key') }.not_to raise_error
678
+ expect { Keyboard.create!(:name => 'Key') }.to raise_error(ActiveRecord::RecordInvalid)
679
+ end
680
+
681
+ it 'should work correctly when using validations with using syntax' do
682
+ @key = Keyboard.using(:brazil).create!(:name => 'Key')
683
+ expect { Keyboard.create!(:name => 'Key') }.not_to raise_error
684
+ expect { Keyboard.using(:brazil).create!(:name => 'Key') }
685
+ .to raise_error(ActiveRecord::RecordInvalid)
686
+ end
687
+ end
688
+ end