ar-octopus 0.8.5 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +6 -9
  4. data/Appraisals +8 -8
  5. data/README.mkdn +47 -15
  6. data/Rakefile +1 -0
  7. data/ar-octopus.gemspec +9 -12
  8. data/gemfiles/rails42.gemfile +2 -2
  9. data/gemfiles/{rails32.gemfile → rails5.gemfile} +2 -2
  10. data/gemfiles/{rails4.gemfile → rails51.gemfile} +2 -2
  11. data/gemfiles/{rails41.gemfile → rails52.gemfile} +2 -2
  12. data/lib/octopus/abstract_adapter.rb +4 -10
  13. data/lib/octopus/association.rb +1 -0
  14. data/lib/octopus/association_shard_tracking.rb +41 -71
  15. data/lib/octopus/collection_association.rb +9 -3
  16. data/lib/octopus/exception.rb +4 -0
  17. data/lib/octopus/finder_methods.rb +8 -0
  18. data/lib/octopus/load_balancing/round_robin.rb +2 -1
  19. data/lib/octopus/log_subscriber.rb +6 -2
  20. data/lib/octopus/migration.rb +123 -55
  21. data/lib/octopus/model.rb +42 -27
  22. data/lib/octopus/persistence.rb +33 -27
  23. data/lib/octopus/proxy.rb +147 -272
  24. data/lib/octopus/proxy_config.rb +251 -0
  25. data/lib/octopus/query_cache_for_shards.rb +24 -0
  26. data/lib/octopus/railtie.rb +0 -2
  27. data/lib/octopus/relation_proxy.rb +36 -1
  28. data/lib/octopus/result_patch.rb +19 -0
  29. data/lib/octopus/scope_proxy.rb +12 -5
  30. data/lib/octopus/shard_tracking.rb +8 -3
  31. data/lib/octopus/slave_group.rb +3 -3
  32. data/lib/octopus/version.rb +1 -1
  33. data/lib/octopus.rb +71 -18
  34. data/spec/config/shards.yml +12 -0
  35. data/spec/migrations/10_create_users_using_replication.rb +1 -1
  36. data/spec/migrations/11_add_field_in_all_slaves.rb +1 -1
  37. data/spec/migrations/12_create_users_using_block.rb +1 -1
  38. data/spec/migrations/13_create_users_using_block_and_using.rb +1 -1
  39. data/spec/migrations/14_create_users_on_shards_of_a_group_with_versions.rb +1 -1
  40. data/spec/migrations/15_create_user_on_shards_of_default_group_with_versions.rb +1 -1
  41. data/spec/migrations/1_create_users_on_master.rb +1 -1
  42. data/spec/migrations/2_create_users_on_canada.rb +1 -1
  43. data/spec/migrations/3_create_users_on_both_shards.rb +1 -1
  44. data/spec/migrations/4_create_users_on_shards_of_a_group.rb +1 -1
  45. data/spec/migrations/5_create_users_on_multiples_groups.rb +1 -1
  46. data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +1 -1
  47. data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +1 -1
  48. data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +1 -1
  49. data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +1 -1
  50. data/spec/octopus/association_shard_tracking_spec.rb +344 -16
  51. data/spec/octopus/load_balancing/round_robin_spec.rb +15 -0
  52. data/spec/octopus/log_subscriber_spec.rb +1 -1
  53. data/spec/octopus/migration_spec.rb +45 -11
  54. data/spec/octopus/model_spec.rb +204 -16
  55. data/spec/octopus/octopus_spec.rb +2 -2
  56. data/spec/octopus/proxy_spec.rb +44 -40
  57. data/spec/octopus/query_cache_for_shards_spec.rb +40 -0
  58. data/spec/octopus/relation_proxy_spec.rb +71 -23
  59. data/spec/octopus/replicated_slave_grouped_spec.rb +27 -0
  60. data/spec/octopus/replication_spec.rb +72 -2
  61. data/spec/octopus/scope_proxy_spec.rb +41 -7
  62. data/spec/spec_helper.rb +2 -0
  63. data/spec/support/database_connection.rb +1 -1
  64. data/spec/support/database_models.rb +1 -1
  65. data/spec/support/octopus_helper.rb +14 -6
  66. data/spec/tasks/octopus.rake_spec.rb +1 -1
  67. metadata +40 -30
  68. data/.ruby-version +0 -1
  69. data/init.rb +0 -1
  70. data/lib/octopus/has_and_belongs_to_many_association.rb +0 -9
  71. data/rails/init.rb +0 -1
@@ -2,6 +2,10 @@ require 'spec_helper'
2
2
 
3
3
  describe Octopus::Model do
4
4
  describe '#using method' do
5
+ it 'raise when Model#using receives a block' do
6
+ expect { User.using(:master) { true } }.to raise_error(Octopus::Exception, /User\.using is not allowed to receive a block/)
7
+ end
8
+
5
9
  it 'should allow to send a block to the master shard' do
6
10
  Octopus.using(:master) do
7
11
  User.create!(:name => 'Block test')
@@ -16,6 +20,16 @@ describe Octopus::Model do
16
20
  expect(User.using('canada').find_by_name('Rafael Pilha')).not_to be_nil
17
21
  end
18
22
 
23
+ it 'should allow comparison of a string shard name with symbol shard name' do
24
+ u = User.using('canada').create!(:name => 'Rafael Pilha')
25
+ expect(u).to eq(User.using(:canada).find_by_name('Rafael Pilha'))
26
+ end
27
+
28
+ it 'should allow comparison of a symbol shard name with string shard name' do
29
+ u = User.using(:canada).create!(:name => 'Rafael Pilha')
30
+ expect(u).to eq(User.using('canada').find_by_name('Rafael Pilha'))
31
+ end
32
+
19
33
  it 'should allow to pass a string as the shard name to a block' do
20
34
  Octopus.using('canada') do
21
35
  User.create!(:name => 'Rafael Pilha')
@@ -49,6 +63,28 @@ describe Octopus::Model do
49
63
  expect(User.all).to eq([u1])
50
64
  end
51
65
 
66
+ it "should allow the #select method to fetch the correct data when using a block" do
67
+ canadian_user = User.using(:canada).create!(:name => 'Rafael Pilha')
68
+
69
+ Octopus.using('canada') do
70
+ @all_canadian_user_ids = User.select('id').to_a
71
+ end
72
+
73
+ expect(@all_canadian_user_ids).to eq([canadian_user])
74
+ end
75
+
76
+ it "should allow objects to be fetch using different blocks - GH #306" do
77
+ canadian_user = User.using(:canada).create!(:name => 'Rafael Pilha')
78
+
79
+ Octopus.using(:canada) { @users = User.where('id is not null') }
80
+ Octopus.using(:canada) { @user = @users.first }
81
+
82
+ Octopus.using(:canada) { @user2 = User.where('id is not null').first }
83
+
84
+ expect(@user).to eq(canadian_user)
85
+ expect(@user2).to eq(canadian_user)
86
+ end
87
+
52
88
  describe 'multiple calls to the same scope' do
53
89
  it 'works with nil response' do
54
90
  scope = User.using(:canada)
@@ -76,11 +112,26 @@ describe Octopus::Model do
76
112
  Octopus.using(:canada) do
77
113
  fail 'Some Exception'
78
114
  end
79
- end.to raise_error
115
+ end.to raise_error(RuntimeError)
80
116
 
81
117
  expect(ActiveRecord::Base.connection.current_shard).to eq(:master)
82
118
  end
83
119
 
120
+ it 'should ensure that the connection will be cleaned with custom master' do
121
+ OctopusHelper.using_environment :octopus do
122
+ Octopus.config[:master_shard] = :brazil
123
+ expect(ActiveRecord::Base.connection.current_shard).to eq(:brazil)
124
+ expect do
125
+ Octopus.using(:canada) do
126
+ fail 'Some Exception'
127
+ end
128
+ end.to raise_error(RuntimeError)
129
+
130
+ expect(ActiveRecord::Base.connection.current_shard).to eq(:brazil)
131
+ Octopus.config[:master_shard] = nil
132
+ end
133
+ end
134
+
84
135
  it 'should allow creating more than one user' do
85
136
  User.using(:canada).create([{ :name => 'America User 1' }, { :name => 'America User 2' }])
86
137
  User.create!(:name => 'Thiago')
@@ -99,6 +150,15 @@ describe Octopus::Model do
99
150
  expect(User.connection.current_shard).to eq(:master)
100
151
  end
101
152
 
153
+ it 'should clean #current_shard from proxy when using execute' do
154
+ OctopusHelper.using_environment :octopus do
155
+ Octopus.config[:master_shard] = :brazil
156
+ User.using(:canada).connection.execute('select * from users limit 1;')
157
+ expect(User.connection.current_shard).to eq(:brazil)
158
+ Octopus.config[:master_shard] = nil
159
+ end
160
+ end
161
+
102
162
  it 'should allow scoping dynamically' do
103
163
  User.using(:canada).using(:master).using(:canada).create!(:name => 'oi')
104
164
  expect(User.using(:canada).using(:master).count).to eq(0)
@@ -246,15 +306,19 @@ describe Octopus::Model do
246
306
 
247
307
  describe 'using a postgresql shard' do
248
308
  it 'should update the Arel Engine' do
249
- expect(User.using(:postgresql_shard).arel_engine.connection.adapter_name).to eq('PostgreSQL')
250
- expect(User.using(:alone_shard).arel_engine.connection.adapter_name).to eq('Mysql2')
309
+ if Octopus.atleast_rails52?
310
+ expect(User.using(:postgresql_shard).connection.adapter_name).to eq('PostgreSQL')
311
+ expect(User.using(:alone_shard).connection.adapter_name).to eq('Mysql2')
312
+ else
313
+ expect(User.using(:postgresql_shard).arel_engine.connection.adapter_name).to eq('PostgreSQL')
314
+ expect(User.using(:alone_shard).arel_engine.connection.adapter_name).to eq('Mysql2')
315
+ end
251
316
  end
252
317
 
253
318
  it 'should works with writes and reads' do
254
319
  u = User.using(:postgresql_shard).create!(:name => 'PostgreSQL User')
255
320
  expect(User.using(:postgresql_shard).all).to eq([u])
256
321
  expect(User.using(:alone_shard).all).to eq([])
257
- User.connection_handler.connection_pools['ActiveRecord::Base'] = User.connection.instance_variable_get(:@shards)[:master]
258
322
  end
259
323
  end
260
324
 
@@ -263,6 +327,12 @@ describe Octopus::Model do
263
327
  expect(CustomConnection.connection.current_database).to eq('octopus_shard_2')
264
328
  end
265
329
 
330
+ it 'reuses parent model connection' do
331
+ klass = Class.new(CustomConnection)
332
+
333
+ expect(klass.connection).to be klass.connection
334
+ end
335
+
266
336
  it 'should not mess with custom connection table names' do
267
337
  expect(Advert.connection.current_database).to eq('octopus_shard_1')
268
338
  Advert.create!(:name => 'Teste')
@@ -328,6 +398,18 @@ describe Octopus::Model do
328
398
  expect(User.using(:master).maximum(:number)).to eq(12)
329
399
  end
330
400
 
401
+ it 'sum' do
402
+ u = User.using(:brazil).create!(:name => 'Teste', :number => 11)
403
+ v = User.using(:master).create!(:name => 'Teste', :number => 12)
404
+
405
+ expect(User.using(:master).sum(:number)).to eq(12)
406
+ expect(User.using(:brazil).sum(:number)).to eq(11)
407
+
408
+ expect(User.where(id: v.id).sum(:number)).to eq(12)
409
+ expect(User.using(:brazil).where(id: u.id).sum(:number)).to eq(11)
410
+ expect(User.using(:master).where(id: v.id).sum(:number)).to eq(12)
411
+ end
412
+
331
413
  describe 'any?' do
332
414
  before { User.using(:brazil).create!(:name => 'User1') }
333
415
 
@@ -418,19 +500,113 @@ describe Octopus::Model do
418
500
  expect(user.as_json(:except => [:created_at, :updated_at, :id])).to eq('admin' => nil, 'name' => 'User1', 'number' => nil)
419
501
  end
420
502
 
421
- it 'transaction' do
422
- _u = User.create!(:name => 'Thiago')
503
+ describe 'transaction' do
504
+ context 'without assigning a database' do
505
+ it 'works as expected' do
506
+ _u = User.create!(:name => 'Thiago')
423
507
 
424
- expect(User.using(:brazil).count).to eq(0)
425
- expect(User.using(:master).count).to eq(1)
508
+ expect(User.using(:brazil).count).to eq(0)
509
+ expect(User.using(:master).count).to eq(1)
510
+
511
+ User.using(:brazil).transaction do
512
+ expect(User.find_by_name('Thiago')).to be_nil
513
+ User.create!(:name => 'Brazil')
514
+ end
426
515
 
427
- User.using(:brazil).transaction do
428
- expect(User.find_by_name('Thiago')).to be_nil
429
- User.create!(:name => 'Brazil')
516
+ expect(User.using(:brazil).count).to eq(1)
517
+ expect(User.using(:master).count).to eq(1)
518
+ end
430
519
  end
431
520
 
432
- expect(User.using(:brazil).count).to eq(1)
433
- expect(User.using(:master).count).to eq(1)
521
+ context 'when assigning a database' do
522
+ it 'works as expected' do
523
+ klass = User.using(:brazil)
524
+
525
+ klass.transaction do
526
+ klass.create!(:name => 'Brazil')
527
+ end
528
+
529
+ expect(klass.find_by_name('Brazil')).to be_present
530
+ end
531
+ end
532
+ end
533
+
534
+ describe "#finder methods" do
535
+ before(:each) do
536
+ @user1 = User.using(:brazil).create!(:name => 'User1')
537
+ @user2 = User.using(:brazil).create!(:name => 'User2')
538
+ @user3 = User.using(:brazil).create!(:name => 'User3')
539
+ end
540
+
541
+ it "#find_each should work with a block" do
542
+ result_array = []
543
+
544
+ User.using(:brazil).where("name is not NULL").find_each do |user|
545
+ result_array << user
546
+ end
547
+
548
+ expect(result_array).to eq([@user1, @user2, @user3])
549
+ end
550
+
551
+ it "#find_each should work with a where.not(...)" do
552
+ result_array = []
553
+
554
+ User.using(:brazil).where.not(:name => 'User2').find_each do |user|
555
+ result_array << user
556
+ end
557
+
558
+ expect(result_array).to eq([@user1, @user3])
559
+ end
560
+
561
+ it "#find_each should work as an enumerator" do
562
+ result_array = []
563
+
564
+ User.using(:brazil).where("name is not NULL").find_each.each do |user|
565
+ result_array << user
566
+ end
567
+
568
+ expect(result_array).to eq([@user1, @user2, @user3])
569
+ end
570
+
571
+ it "#find_each should work as a lazy enumerator" do
572
+ result_array = []
573
+
574
+ User.using(:brazil).where("name is not NULL").find_each.lazy.each do |user|
575
+ result_array << user
576
+ end
577
+
578
+ expect(result_array).to eq([@user1, @user2, @user3])
579
+ end
580
+
581
+ it "#find_in_batches should work with a block" do
582
+ result_array = []
583
+
584
+ User.using(:brazil).where("name is not NULL").find_in_batches(batch_size: 1) do |user|
585
+ result_array << user
586
+ end
587
+
588
+ expect(result_array).to eq([[@user1], [@user2], [@user3]])
589
+ end
590
+
591
+ it "#find_in_batches should work as an enumerator" do
592
+ result_array = []
593
+
594
+ User.using(:brazil).where("name is not NULL").find_in_batches(batch_size: 1).each do |user|
595
+ result_array << user
596
+ end
597
+
598
+ expect(result_array).to eq([[@user1], [@user2], [@user3]])
599
+ end
600
+
601
+ it "#find_in_batches should work as a lazy enumerator" do
602
+ result_array = []
603
+
604
+ User.using(:brazil).where("name is not NULL").find_in_batches(batch_size: 1).lazy.each do |user|
605
+ result_array << user
606
+ end
607
+
608
+ expect(result_array).to eq([[@user1], [@user2], [@user3]])
609
+ end
434
610
  end
435
611
 
436
612
  describe 'deleting a record' do
@@ -622,20 +798,21 @@ describe Octopus::Model do
622
798
  it 'should work correctly when using validations' do
623
799
  @key = Keyboard.create!(:name => 'Key')
624
800
  expect { Keyboard.using(:brazil).create!(:name => 'Key') }.not_to raise_error
625
- expect { Keyboard.create!(:name => 'Key') }.to raise_error
801
+ expect { Keyboard.create!(:name => 'Key') }.to raise_error(ActiveRecord::RecordInvalid)
626
802
  end
627
803
 
628
804
  it 'should work correctly when using validations with using syntax' do
629
805
  @key = Keyboard.using(:brazil).create!(:name => 'Key')
630
806
  expect { Keyboard.create!(:name => 'Key') }.not_to raise_error
631
- expect { Keyboard.using(:brazil).create!(:name => 'Key') }.to raise_error
807
+ expect { Keyboard.using(:brazil).create!(:name => 'Key') }
808
+ .to raise_error(ActiveRecord::RecordInvalid)
632
809
  end
633
810
  end
634
811
 
635
812
  describe '#replicated_model method' do
636
813
  it 'should be replicated' do
637
814
  OctopusHelper.using_environment :production_replicated do
638
- expect(ActiveRecord::Base.connection_proxy.instance_variable_get(:@replicated)).to be true
815
+ expect(ActiveRecord::Base.connection_proxy.replicated).to be true
639
816
  end
640
817
  end
641
818
 
@@ -645,5 +822,16 @@ describe Octopus::Model do
645
822
  expect(Cat.replicated).to be true
646
823
  end
647
824
  end
825
+
826
+ it "should work on a fully replicated environment" do
827
+ OctopusHelper.using_environment :production_fully_replicated do
828
+ User.using(:slave1).create!(name: 'Thiago')
829
+ User.using(:slave2).create!(name: 'Thiago')
830
+
831
+ replicated_cat = User.find_by_name 'Thiago'
832
+
833
+ expect(replicated_cat.current_shard.to_s).to match(/master/)
834
+ end
835
+ end
648
836
  end
649
837
  end
@@ -37,10 +37,10 @@ describe Octopus, :shards => [] do
37
37
  end
38
38
 
39
39
  it 'should permit users to configure shards on initializer files, instead of on a yml file.' do
40
- expect { User.using(:crazy_shard).create!(:name => 'Joaquim') }.to raise_error
40
+ expect { User.using(:crazy_shard).create!(:name => 'Joaquim') }.to raise_error(RuntimeError)
41
41
 
42
42
  Octopus.setup do |config|
43
- config.shards = { :crazy_shard => { :adapter => 'mysql2', :database => 'octopus_shard_5', :username => 'root', :password => '' } }
43
+ config.shards = { :crazy_shard => { :adapter => 'mysql2', :database => 'octopus_shard_5', :username => "#{ENV['MYSQL_USER'] || 'root'}", :password => '' } }
44
44
  end
45
45
 
46
46
  expect { User.using(:crazy_shard).create!(:name => 'Joaquim') }.not_to raise_error
@@ -6,10 +6,10 @@ describe Octopus::Proxy do
6
6
  describe 'creating a new instance', :shards => [] do
7
7
  it 'should initialize all shards and groups' do
8
8
  # FIXME: Don't test implementation details
9
- expect(proxy.instance_variable_get(:@shards)).to include('canada', 'brazil', 'master', 'sqlite_shard', 'russia', 'alone_shard',
9
+ expect(proxy.shards).to include('canada', 'brazil', 'master', 'sqlite_shard', 'russia', 'alone_shard',
10
10
  'aug2009', 'postgresql_shard', 'aug2010', 'aug2011')
11
11
 
12
- expect(proxy.instance_variable_get(:@shards)).to include('protocol_shard')
12
+ expect(proxy.shards).to include('protocol_shard')
13
13
 
14
14
  expect(proxy.has_group?('country_shards')).to be true
15
15
  expect(proxy.shards_for_group('country_shards')).to include(:canada, :brazil, :russia)
@@ -23,24 +23,20 @@ describe Octopus::Proxy do
23
23
  end
24
24
 
25
25
  it 'should initialize replicated attribute as false' do
26
- expect(proxy.instance_variable_get(:@replicated)).to be_falsey
26
+ expect(proxy.replicated).to be_falsey
27
27
  end
28
28
 
29
- it 'should work with thiking sphinx' do
30
- config = proxy.instance_variable_get(:@config)
29
+ it 'should work with thinking sphinx' do
30
+ config = proxy.config
31
31
  expect(config[:adapter]).to eq('mysql2')
32
32
  expect(config[:database]).to eq('octopus_shard_1')
33
- expect(config[:username]).to eq('root')
33
+ expect(config[:username]).to eq("#{ENV['MYSQL_USER'] || 'root'}")
34
34
  end
35
35
 
36
- it 'should create a set with all adapters, to ensure that is needed to clean the table name.' do
37
- adapters = proxy.instance_variable_get(:@adapters)
38
- expect(adapters).to be_kind_of(Set)
39
- expect(adapters.to_a).to match_array(%w(sqlite3 mysql2 postgresql))
40
- end
41
-
42
- it 'should respond correctly to respond_to?(:pk_and_sequence_for)' do
43
- expect(proxy.respond_to?(:pk_and_sequence_for)).to be true
36
+ unless Octopus.rails50? || Octopus.rails51?|| Octopus.rails52?
37
+ it 'should respond correctly to respond_to?(:pk_and_sequence_for)' do
38
+ expect(proxy.respond_to?(:pk_and_sequence_for)).to be true
39
+ end
44
40
  end
45
41
 
46
42
  it 'should respond correctly to respond_to?(:primary_key)' do
@@ -61,22 +57,6 @@ describe Octopus::Proxy do
61
57
  end
62
58
  end
63
59
 
64
- describe '#should_clean_table_name?' do
65
- it 'should return true when you have a environment with multiple database types' do
66
- expect(proxy.should_clean_table_name?).to be true
67
- end
68
-
69
- context 'when using a environment with a single table name' do
70
- before(:each) do
71
- OctopusHelper.octopus_env = 'production_replicated'
72
- end
73
-
74
- it 'should return false' do
75
- expect(proxy.should_clean_table_name?).to be false
76
- end
77
- end
78
- end
79
-
80
60
  describe 'should raise error if you have duplicated shard names' do
81
61
  before(:each) do
82
62
  OctopusHelper.octopus_env = 'production_raise_error'
@@ -93,11 +73,11 @@ describe Octopus::Proxy do
93
73
  end
94
74
 
95
75
  it 'should initialize just the master shard' do
96
- expect(proxy.instance_variable_get(:@shards).keys).to eq(['master'])
76
+ expect(proxy.shards.keys).to eq(['master'])
97
77
  end
98
78
 
99
79
  it 'should not initialize replication' do
100
- expect(proxy.instance_variable_get(:@replicated)).to be_nil
80
+ expect(proxy.replicated).to be_nil
101
81
  end
102
82
  end
103
83
  end
@@ -108,11 +88,11 @@ describe Octopus::Proxy do
108
88
  end
109
89
 
110
90
  it 'should have the replicated attribute as true' do
111
- expect(proxy.instance_variable_get(:@replicated)).to be true
91
+ expect(proxy.replicated).to be true
112
92
  end
113
93
 
114
94
  it 'should initialize the list of shards' do
115
- expect(proxy.instance_variable_get(:@slaves_list)).to eq(%w(slave1 slave2 slave3 slave4))
95
+ expect(proxy.slaves_list).to eq(%w(slave1 slave2 slave3 slave4))
116
96
  end
117
97
  end
118
98
 
@@ -135,7 +115,7 @@ describe Octopus::Proxy do
135
115
  Octopus.instance_variable_set(:@environments, nil)
136
116
  Octopus.config
137
117
 
138
- expect(proxy.instance_variable_get(:@replicated)).to be true
118
+ expect(proxy.replicated).to be true
139
119
  expect(Octopus.environments).to eq(%w(staging production))
140
120
  end
141
121
 
@@ -145,7 +125,7 @@ describe Octopus::Proxy do
145
125
  Octopus.instance_variable_set(:@environments, nil)
146
126
  Octopus.config
147
127
 
148
- expect(proxy.instance_variable_get(:@shards).keys.to_set).to eq(Set.new(%w(slave1 slave2 master)))
128
+ expect(proxy.shards.keys.to_set).to eq(Set.new(%w(slave1 slave2 master)))
149
129
  end
150
130
 
151
131
  it 'should initialize correctly the shard octopus_shard value for logging' do
@@ -154,7 +134,7 @@ describe Octopus::Proxy do
154
134
  Octopus.instance_variable_set(:@environments, nil)
155
135
  Octopus.config
156
136
 
157
- expect(proxy.instance_variable_get(:@shards)['slave1'].spec.config).to have_key :octopus_shard
137
+ expect(proxy.shards['slave1'].spec.config).to have_key :octopus_shard
158
138
  end
159
139
 
160
140
  it 'should initialize correctly the shards for the production environment' do
@@ -163,7 +143,7 @@ describe Octopus::Proxy do
163
143
  Octopus.instance_variable_set(:@environments, nil)
164
144
  Octopus.config
165
145
 
166
- expect(proxy.instance_variable_get(:@shards).keys.to_set).to eq(Set.new(%w(slave3 slave4 master)))
146
+ expect(proxy.shards.keys.to_set).to eq(Set.new(%w(slave3 slave4 master)))
167
147
  end
168
148
 
169
149
  describe 'using the master connection', :shards => [:russia, :master] do
@@ -211,6 +191,14 @@ describe Octopus::Proxy do
211
191
  expect(proxy.shard_name).to eq(:master)
212
192
  end
213
193
 
194
+ it 'when current_shard is empty with custom master' do
195
+ OctopusHelper.using_environment :octopus do
196
+ Octopus.config[:master_shard] = :brazil
197
+ expect(proxy.shard_name).to eq(:brazil)
198
+ Octopus.config[:master_shard] = nil
199
+ end
200
+ end
201
+
214
202
  it 'when current_shard is a single shard' do
215
203
  proxy.current_shard = :canada
216
204
  expect(proxy.shard_name).to eq(:canada)
@@ -224,12 +212,12 @@ describe Octopus::Proxy do
224
212
 
225
213
  describe 'should return the connection based on shard_name' do
226
214
  it 'when current_shard is empty' do
227
- expect(proxy.select_connection).to eq(proxy.instance_variable_get(:@shards)[:master].connection)
215
+ expect(proxy.select_connection).to eq(proxy.shards[:master].connection)
228
216
  end
229
217
 
230
218
  it 'when current_shard is a single shard' do
231
219
  proxy.current_shard = :canada
232
- expect(proxy.select_connection).to eq(proxy.instance_variable_get(:@shards)[:canada].connection)
220
+ expect(proxy.select_connection).to eq(proxy.shards[:canada].connection)
233
221
  end
234
222
  end
235
223
  end
@@ -264,6 +252,22 @@ describe Octopus::Proxy do
264
252
  end
265
253
  end
266
254
 
255
+
256
+ describe 'cleaning the connection proxy' do
257
+ it 'should not clean #current_shard from proxy when using a block and calling #execute' do
258
+ Octopus.using(:canada) do
259
+ expect(User.connection.current_shard).to eq(:canada)
260
+
261
+ connection = User.connection
262
+
263
+ result = connection.execute('select * from users limit 1;')
264
+ result = connection.execute('select * from users limit 1;')
265
+
266
+ expect(User.connection.current_shard).to eq(:canada)
267
+ end
268
+ end
269
+ end
270
+
267
271
  describe 'connection reuse' do
268
272
  before :each do
269
273
  @item_brazil_conn = Item.using(:brazil).new(:name => 'Brazil Item').class.connection.select_connection
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ unless Octopus.rails4? || Octopus.rails50?
4
+ describe Octopus::ConnectionPool::QueryCacheForShards do
5
+ subject(:query_cache_on_shard) { ActiveRecord::Base.using(:brazil).connection.query_cache_enabled }
6
+
7
+ context 'Octopus enabled' do
8
+ context 'when query cache is enabled on the primary connection_pool' do
9
+ before { ActiveRecord::Base.connection_pool.enable_query_cache! }
10
+ it { is_expected.to be true }
11
+ end
12
+
13
+ context 'when query cache is disabled on the primary connection_pool' do
14
+ before { ActiveRecord::Base.connection_pool.disable_query_cache! }
15
+ it { is_expected.to be false }
16
+ end
17
+ end
18
+
19
+ context 'Octopus disabled' do
20
+ before do
21
+ Rails = double
22
+ allow(Rails).to receive(:env).and_return('staging')
23
+ end
24
+
25
+ after do
26
+ Object.send(:remove_const, :Rails)
27
+ end
28
+
29
+ context 'when query cache is enabled on the primary connection_pool' do
30
+ before { ActiveRecord::Base.connection_pool.enable_query_cache! }
31
+ it { is_expected.to be true }
32
+ end
33
+
34
+ context 'when query cache is disabled on the primary connection_pool' do
35
+ before { ActiveRecord::Base.connection_pool.disable_query_cache! }
36
+ it { is_expected.to be false }
37
+ end
38
+ end
39
+ end
40
+ end
@@ -12,10 +12,39 @@ describe Octopus::RelationProxy do
12
12
  expect(@relation.current_shard).to eq(:canada)
13
13
  end
14
14
 
15
- unless Octopus.rails3?
16
- it 'can define collection association with the same name as ancestor private method' do
17
- @client.comments << Comment.using(:canada).create!(open: true)
18
- expect(@client.comments.open).to be_a_kind_of(ActiveRecord::Relation)
15
+ it 'can define collection association with the same name as ancestor private method' do
16
+ @client.comments << Comment.using(:canada).create!(open: true)
17
+ expect(@client.comments.open).to be_a_kind_of(ActiveRecord::Relation)
18
+ end
19
+
20
+ it 'can be dumped and loaded' do
21
+ expect(Marshal.load(Marshal.dump(@relation))).to eq @relation
22
+ end
23
+
24
+ it 'maintains the current shard when using where.not(...)' do
25
+ where_chain = @relation.where
26
+ expect(where_chain.current_shard).to eq(@relation.current_shard)
27
+ not_relation = where_chain.not("1=0")
28
+ expect(not_relation.current_shard).to eq(@relation.current_shard)
29
+ end
30
+
31
+ context 'when a new relation is constructed from the original relation' do
32
+ context 'and a where(...) is used' do
33
+ it 'does not tamper with the original relation' do
34
+ relation = Item.using(:canada).where(id: 1)
35
+ original_sql = relation.to_sql
36
+ new_relation = relation.where(id: 2)
37
+ expect(relation.to_sql).to eq(original_sql)
38
+ end
39
+ end
40
+
41
+ context 'and a where.not(...) is used' do
42
+ it 'does not tamper with the original relation' do
43
+ relation = Item.using(:canada).where(id: 1)
44
+ original_sql = relation.to_sql
45
+ new_relation = relation.where.not(id: 2)
46
+ expect(relation.to_sql).to eq(original_sql)
47
+ end
19
48
  end
20
49
  end
21
50
 
@@ -29,26 +58,36 @@ describe Octopus::RelationProxy do
29
58
  end
30
59
  end
31
60
 
32
- if Octopus.rails4?
33
- context 'under Rails 4' do
34
- it 'is an Octopus::RelationProxy' do
35
- expect{@relation.ar_relation}.not_to raise_error
36
- end
61
+ it "can deliver methods in ActiveRecord::Batches correctly when given a block" do
62
+ expect { @relation.find_each(&:inspect) }.not_to raise_error
63
+ end
37
64
 
38
- it 'should be able to return its ActiveRecord::Relation' do
39
- expect(@relation.ar_relation.is_a?(ActiveRecord::Relation)).to be true
40
- end
65
+ it "can deliver methods in ActiveRecord::Batches correctly as an enumerator" do
66
+ expect { @relation.find_each.each(&:inspect) }.not_to raise_error
67
+ end
41
68
 
42
- it 'is equal to an identically-defined, but different, RelationProxy' do
43
- i = @client.items
44
- expect(@relation).to eq(i)
45
- expect(@relation.__id__).not_to eq(i.__id__)
46
- end
69
+ it "can deliver methods in ActiveRecord::Batches correctly as a lazy enumerator" do
70
+ expect { @relation.find_each.lazy.each(&:inspect) }.not_to raise_error
71
+ end
47
72
 
48
- it 'is equal to its own underlying ActiveRecord::Relation' do
49
- expect(@relation).to eq(@relation.ar_relation)
50
- expect(@relation.ar_relation).to eq(@relation)
51
- end
73
+ context 'under Rails 4' do
74
+ it 'is an Octopus::RelationProxy' do
75
+ expect{@relation.ar_relation}.not_to raise_error
76
+ end
77
+
78
+ it 'should be able to return its ActiveRecord::Relation' do
79
+ expect(@relation.ar_relation.is_a?(ActiveRecord::Relation)).to be true
80
+ end
81
+
82
+ it 'is equal to an identically-defined, but different, RelationProxy' do
83
+ i = @client.items
84
+ expect(@relation).to eq(i)
85
+ expect(@relation.__id__).not_to eq(i.__id__)
86
+ end
87
+
88
+ it 'is equal to its own underlying ActiveRecord::Relation' do
89
+ expect(@relation).to eq(@relation.ar_relation)
90
+ expect(@relation.ar_relation).to eq(@relation)
52
91
  end
53
92
  end
54
93
 
@@ -68,14 +107,23 @@ describe Octopus::RelationProxy do
68
107
  it 'uses the correct shard' do
69
108
  expect(Item.using(:brazil).count).to eq(0)
70
109
  _clients_on_brazil = Client.using(:brazil).all
71
- Client.using(:brazil) do
110
+ Octopus.using(:brazil) do
72
111
  expect(@relation.count).to eq(1)
73
112
  end
74
113
  end
75
114
 
115
+ it 'uses the correct shard in block when method_missing is triggered on CollectionProxy objects' do
116
+ Octopus.using(:brazil) do
117
+ @client.items.each do |item|
118
+ expect(item.current_shard).to eq(:canada)
119
+ expect(ActiveRecord::Base.connection.current_shard).to eq(:brazil)
120
+ end
121
+ end
122
+ end
123
+
76
124
  it 'lazily evaluates on the correct shard' do
77
125
  expect(Item.using(:brazil).count).to eq(0)
78
- Client.using(:brazil) do
126
+ Octopus.using(:brazil) do
79
127
  expect(@relation.select(:client_id).count).to eq(1)
80
128
  end
81
129
  end