ar-octopus 0.8.5 → 0.10.2

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.
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