ar-octopus-ruby-3 0.11.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 (160) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +46 -0
  5. data/.rubocop_todo.yml +56 -0
  6. data/.travis.yml +18 -0
  7. data/Appraisals +16 -0
  8. data/Gemfile +4 -0
  9. data/README.mkdn +257 -0
  10. data/Rakefile +175 -0
  11. data/TODO.txt +7 -0
  12. data/ar-octopus.gemspec +44 -0
  13. data/gemfiles/rails42.gemfile +7 -0
  14. data/gemfiles/rails5.gemfile +7 -0
  15. data/gemfiles/rails51.gemfile +7 -0
  16. data/gemfiles/rails52.gemfile +7 -0
  17. data/lib/ar-octopus.rb +1 -0
  18. data/lib/octopus/abstract_adapter.rb +33 -0
  19. data/lib/octopus/association.rb +14 -0
  20. data/lib/octopus/association_shard_tracking.rb +74 -0
  21. data/lib/octopus/collection_association.rb +17 -0
  22. data/lib/octopus/collection_proxy.rb +16 -0
  23. data/lib/octopus/exception.rb +4 -0
  24. data/lib/octopus/finder_methods.rb +8 -0
  25. data/lib/octopus/load_balancing/round_robin.rb +20 -0
  26. data/lib/octopus/load_balancing.rb +4 -0
  27. data/lib/octopus/log_subscriber.rb +26 -0
  28. data/lib/octopus/migration.rb +236 -0
  29. data/lib/octopus/model.rb +216 -0
  30. data/lib/octopus/persistence.rb +45 -0
  31. data/lib/octopus/proxy.rb +399 -0
  32. data/lib/octopus/proxy_config.rb +251 -0
  33. data/lib/octopus/query_cache_for_shards.rb +24 -0
  34. data/lib/octopus/railtie.rb +11 -0
  35. data/lib/octopus/relation_proxy.rb +74 -0
  36. data/lib/octopus/result_patch.rb +19 -0
  37. data/lib/octopus/scope_proxy.rb +68 -0
  38. data/lib/octopus/shard_tracking/attribute.rb +22 -0
  39. data/lib/octopus/shard_tracking/dynamic.rb +11 -0
  40. data/lib/octopus/shard_tracking.rb +46 -0
  41. data/lib/octopus/singular_association.rb +9 -0
  42. data/lib/octopus/slave_group.rb +13 -0
  43. data/lib/octopus/version.rb +3 -0
  44. data/lib/octopus.rb +209 -0
  45. data/lib/tasks/octopus.rake +16 -0
  46. data/sample_app/.gitignore +4 -0
  47. data/sample_app/.rspec +1 -0
  48. data/sample_app/Gemfile +20 -0
  49. data/sample_app/Gemfile.lock +155 -0
  50. data/sample_app/README +3 -0
  51. data/sample_app/README.rdoc +261 -0
  52. data/sample_app/Rakefile +7 -0
  53. data/sample_app/app/assets/images/rails.png +0 -0
  54. data/sample_app/app/assets/javascripts/application.js +15 -0
  55. data/sample_app/app/assets/stylesheets/application.css +13 -0
  56. data/sample_app/app/controllers/application_controller.rb +4 -0
  57. data/sample_app/app/helpers/application_helper.rb +2 -0
  58. data/sample_app/app/mailers/.gitkeep +0 -0
  59. data/sample_app/app/models/.gitkeep +0 -0
  60. data/sample_app/app/models/item.rb +3 -0
  61. data/sample_app/app/models/user.rb +3 -0
  62. data/sample_app/app/views/layouts/application.html.erb +14 -0
  63. data/sample_app/autotest/discover.rb +2 -0
  64. data/sample_app/config/application.rb +62 -0
  65. data/sample_app/config/boot.rb +6 -0
  66. data/sample_app/config/cucumber.yml +8 -0
  67. data/sample_app/config/database.yml +28 -0
  68. data/sample_app/config/environment.rb +5 -0
  69. data/sample_app/config/environments/development.rb +37 -0
  70. data/sample_app/config/environments/production.rb +67 -0
  71. data/sample_app/config/environments/test.rb +37 -0
  72. data/sample_app/config/initializers/backtrace_silencers.rb +7 -0
  73. data/sample_app/config/initializers/inflections.rb +15 -0
  74. data/sample_app/config/initializers/mime_types.rb +5 -0
  75. data/sample_app/config/initializers/secret_token.rb +7 -0
  76. data/sample_app/config/initializers/session_store.rb +8 -0
  77. data/sample_app/config/initializers/wrap_parameters.rb +14 -0
  78. data/sample_app/config/locales/en.yml +5 -0
  79. data/sample_app/config/routes.rb +58 -0
  80. data/sample_app/config/shards.yml +28 -0
  81. data/sample_app/config.ru +4 -0
  82. data/sample_app/db/migrate/20100720172715_create_users.rb +15 -0
  83. data/sample_app/db/migrate/20100720172730_create_items.rb +16 -0
  84. data/sample_app/db/migrate/20100720210335_create_sample_users.rb +11 -0
  85. data/sample_app/db/schema.rb +29 -0
  86. data/sample_app/db/seeds.rb +16 -0
  87. data/sample_app/doc/README_FOR_APP +2 -0
  88. data/sample_app/features/migrate.feature +45 -0
  89. data/sample_app/features/seed.feature +15 -0
  90. data/sample_app/features/step_definitions/seeds_steps.rb +13 -0
  91. data/sample_app/features/step_definitions/web_steps.rb +218 -0
  92. data/sample_app/features/support/database.rb +13 -0
  93. data/sample_app/features/support/env.rb +57 -0
  94. data/sample_app/features/support/paths.rb +33 -0
  95. data/sample_app/lib/assets/.gitkeep +0 -0
  96. data/sample_app/lib/tasks/.gitkeep +0 -0
  97. data/sample_app/lib/tasks/cucumber.rake +64 -0
  98. data/sample_app/log/.gitkeep +0 -0
  99. data/sample_app/public/404.html +26 -0
  100. data/sample_app/public/422.html +26 -0
  101. data/sample_app/public/500.html +26 -0
  102. data/sample_app/public/favicon.ico +0 -0
  103. data/sample_app/public/images/rails.png +0 -0
  104. data/sample_app/public/index.html +279 -0
  105. data/sample_app/public/javascripts/application.js +2 -0
  106. data/sample_app/public/javascripts/controls.js +965 -0
  107. data/sample_app/public/javascripts/dragdrop.js +974 -0
  108. data/sample_app/public/javascripts/effects.js +1123 -0
  109. data/sample_app/public/javascripts/prototype.js +4874 -0
  110. data/sample_app/public/javascripts/rails.js +118 -0
  111. data/sample_app/public/robots.txt +5 -0
  112. data/sample_app/public/stylesheets/.gitkeep +0 -0
  113. data/sample_app/script/cucumber +10 -0
  114. data/sample_app/script/rails +6 -0
  115. data/sample_app/spec/models/item_spec.rb +5 -0
  116. data/sample_app/spec/models/user_spec.rb +5 -0
  117. data/sample_app/spec/spec_helper.rb +27 -0
  118. data/sample_app/vendor/assets/javascripts/.gitkeep +0 -0
  119. data/sample_app/vendor/assets/stylesheets/.gitkeep +0 -0
  120. data/sample_app/vendor/plugins/.gitkeep +0 -0
  121. data/spec/config/shards.yml +231 -0
  122. data/spec/migrations/10_create_users_using_replication.rb +9 -0
  123. data/spec/migrations/11_add_field_in_all_slaves.rb +11 -0
  124. data/spec/migrations/12_create_users_using_block.rb +23 -0
  125. data/spec/migrations/13_create_users_using_block_and_using.rb +15 -0
  126. data/spec/migrations/14_create_users_on_shards_of_a_group_with_versions.rb +11 -0
  127. data/spec/migrations/15_create_user_on_shards_of_default_group_with_versions.rb +9 -0
  128. data/spec/migrations/1_create_users_on_master.rb +9 -0
  129. data/spec/migrations/2_create_users_on_canada.rb +11 -0
  130. data/spec/migrations/3_create_users_on_both_shards.rb +11 -0
  131. data/spec/migrations/4_create_users_on_shards_of_a_group.rb +11 -0
  132. data/spec/migrations/5_create_users_on_multiples_groups.rb +11 -0
  133. data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +11 -0
  134. data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +11 -0
  135. data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +11 -0
  136. data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +11 -0
  137. data/spec/octopus/association_shard_tracking_spec.rb +1036 -0
  138. data/spec/octopus/collection_proxy_spec.rb +16 -0
  139. data/spec/octopus/load_balancing/round_robin_spec.rb +15 -0
  140. data/spec/octopus/log_subscriber_spec.rb +19 -0
  141. data/spec/octopus/migration_spec.rb +151 -0
  142. data/spec/octopus/model_spec.rb +837 -0
  143. data/spec/octopus/octopus_spec.rb +123 -0
  144. data/spec/octopus/proxy_spec.rb +303 -0
  145. data/spec/octopus/query_cache_for_shards_spec.rb +40 -0
  146. data/spec/octopus/relation_proxy_spec.rb +132 -0
  147. data/spec/octopus/replicated_slave_grouped_spec.rb +91 -0
  148. data/spec/octopus/replication_spec.rb +196 -0
  149. data/spec/octopus/scope_proxy_spec.rb +97 -0
  150. data/spec/octopus/sharded_replicated_slave_grouped_spec.rb +55 -0
  151. data/spec/octopus/sharded_spec.rb +33 -0
  152. data/spec/spec_helper.rb +18 -0
  153. data/spec/support/active_record/connection_adapters/modify_config_adapter.rb +15 -0
  154. data/spec/support/database_connection.rb +4 -0
  155. data/spec/support/database_models.rb +118 -0
  156. data/spec/support/octopus_helper.rb +66 -0
  157. data/spec/support/query_count.rb +17 -0
  158. data/spec/support/shared_contexts.rb +18 -0
  159. data/spec/tasks/octopus.rake_spec.rb +32 -0
  160. metadata +351 -0
@@ -0,0 +1,123 @@
1
+ require 'spec_helper'
2
+
3
+ describe Octopus, :shards => [] do
4
+ describe '#config' do
5
+ it 'should load shards.yml file to start working' do
6
+ expect(Octopus.config).to be_kind_of(HashWithIndifferentAccess)
7
+ end
8
+
9
+ describe "when config file doesn't exist" do
10
+ before(:each) do
11
+ allow(Octopus).to receive(:directory).and_return('/tmp')
12
+ Octopus.instance_variable_set(:@config, nil)
13
+ end
14
+
15
+ it 'should return an empty HashWithIndifferentAccess' do
16
+ expect(Octopus.config).to eq(HashWithIndifferentAccess.new)
17
+ end
18
+ end
19
+ end
20
+
21
+ describe '#directory' do
22
+ it 'should return the directory that contains the shards.yml file' do
23
+ expect(Octopus.directory).to eq(File.expand_path(File.dirname(__FILE__) + '/../'))
24
+ end
25
+ end
26
+
27
+ describe '#env' do
28
+ it "should return 'production' when is outside of a rails application" do
29
+ expect(Octopus.env).to eq('octopus')
30
+ end
31
+ end
32
+
33
+ describe '#shards=' do
34
+ after(:each) do
35
+ Octopus.instance_variable_set(:@config, nil)
36
+ Octopus::Model.send(:class_variable_set, :@@connection_proxy, nil)
37
+ end
38
+
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(RuntimeError)
41
+
42
+ Octopus.setup do |config|
43
+ config.shards = { :crazy_shard => { :adapter => 'mysql2', :database => 'octopus_shard_5', :username => "#{ENV['MYSQL_USER'] || 'root'}", :password => ENV['MYSQL_PASSWORD'] } }
44
+ end
45
+
46
+ expect { User.using(:crazy_shard).create!(:name => 'Joaquim') }.not_to raise_error
47
+ end
48
+ end
49
+
50
+ describe '#setup' do
51
+ it 'should have the default octopus environment as production' do
52
+ expect(Octopus.environments).to eq(['production'])
53
+ end
54
+
55
+ it 'should allow the user to configure the octopus environments' do
56
+ Octopus.setup do |config|
57
+ config.environments = [:production, :staging]
58
+ end
59
+
60
+ expect(Octopus.environments).to eq(%w(production staging))
61
+
62
+ Octopus.setup do |config|
63
+ config.environments = [:production]
64
+ end
65
+ end
66
+ end
67
+
68
+ describe '#enabled?' do
69
+ before do
70
+ Rails = double
71
+ end
72
+
73
+ after do
74
+ Object.send(:remove_const, :Rails)
75
+ end
76
+
77
+ it 'should be if octopus is configured and should hook into current environment' do
78
+ allow(Rails).to receive(:env).and_return('production')
79
+
80
+ expect(Octopus).to be_enabled
81
+ end
82
+
83
+ it 'should not be if octopus should not hook into current environment' do
84
+ allow(Rails).to receive(:env).and_return('staging')
85
+
86
+ expect(Octopus).not_to be_enabled
87
+ end
88
+ end
89
+
90
+ describe '#fully_replicated' do
91
+ before do
92
+ OctopusHelper.using_environment :production_replicated do
93
+ OctopusHelper.clean_all_shards([:slave1, :slave2, :slave3, :slave4])
94
+ 4.times { |i| User.using(:"slave#{i + 1}").create!(:name => 'Slave User') }
95
+ end
96
+ end
97
+
98
+ it 'sends queries to slaves' do
99
+ OctopusHelper.using_environment :production_replicated do
100
+ expect(User.count).to eq(0)
101
+ 4.times do |_i|
102
+ Octopus.fully_replicated do
103
+ expect(User.count).to eq(1)
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ it 'allows nesting' do
110
+ OctopusHelper.using_environment :production_replicated do
111
+ Octopus.fully_replicated do
112
+ expect(User.count).to eq(1)
113
+
114
+ Octopus.fully_replicated do
115
+ expect(User.count).to eq(1)
116
+ end
117
+
118
+ expect(User.count).to eq(1)
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,303 @@
1
+ require 'spec_helper'
2
+
3
+ describe Octopus::Proxy do
4
+ let(:proxy) { subject }
5
+
6
+ describe 'creating a new instance', :shards => [] do
7
+ it 'should initialize all shards and groups' do
8
+ # FIXME: Don't test implementation details
9
+ expect(proxy.shards).to include('canada', 'brazil', 'master', 'sqlite_shard', 'russia', 'alone_shard',
10
+ 'aug2009', 'postgresql_shard', 'aug2010', 'aug2011')
11
+
12
+ expect(proxy.shards).to include('protocol_shard')
13
+
14
+ expect(proxy.has_group?('country_shards')).to be true
15
+ expect(proxy.shards_for_group('country_shards')).to include(:canada, :brazil, :russia)
16
+
17
+ expect(proxy.has_group?('history_shards')).to be true
18
+ expect(proxy.shards_for_group('history_shards')).to include(:aug2009, :aug2010, :aug2011)
19
+ end
20
+
21
+ it 'should initialize the block attribute as false' do
22
+ expect(proxy.block).to be_falsey
23
+ end
24
+
25
+ it 'should initialize replicated attribute as false' do
26
+ expect(proxy.replicated).to be_falsey
27
+ end
28
+
29
+ it 'should work with thinking sphinx' do
30
+ config = proxy.config
31
+ expect(config[:adapter]).to eq('mysql2')
32
+ expect(config[:database]).to eq('octopus_shard_1')
33
+ expect(config[:username]).to eq("#{ENV['MYSQL_USER'] || 'root'}")
34
+ end
35
+
36
+ unless Octopus.rails50? || Octopus.rails51?|| Octopus.rails52? || Octopus.rails60?
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
40
+ end
41
+
42
+ it 'should respond correctly to respond_to?(:primary_key)' do
43
+ expect(proxy.respond_to?(:primary_key)).to be true
44
+ end
45
+
46
+ context 'when an adapter that modifies the config' do
47
+ before { OctopusHelper.octopus_env = 'modify_config' }
48
+ after { OctopusHelper.octopus_env = 'octopus' }
49
+
50
+ it 'should not fail with missing adapter second time round' do
51
+ skip 'This test was actually failing because of a typo in the error message.'
52
+ Thread.current['octopus.current_shard'] = :modify_config_read
53
+
54
+ expect { Octopus::Proxy.new(Octopus.config) }.not_to raise_error
55
+
56
+ Thread.current['octopus.current_shard'] = nil
57
+ end
58
+ end
59
+
60
+ describe 'should raise error if you have duplicated shard names' do
61
+ before(:each) do
62
+ OctopusHelper.octopus_env = 'production_raise_error'
63
+ end
64
+
65
+ it 'should raise the error' do
66
+ expect { proxy }.to raise_error('You have duplicated shard names!')
67
+ end
68
+ end
69
+
70
+ describe "should initialize just the master when you don't have a shards.yml file" do
71
+ before(:each) do
72
+ OctopusHelper.octopus_env = 'crazy_environment'
73
+ end
74
+
75
+ it 'should initialize just the master shard' do
76
+ expect(proxy.shards.keys).to eq(['master'])
77
+ end
78
+
79
+ it 'should not initialize replication' do
80
+ expect(proxy.replicated).to be_nil
81
+ end
82
+ end
83
+ end
84
+
85
+ describe 'when you have a replicated environment' do
86
+ before(:each) do
87
+ OctopusHelper.octopus_env = 'production_replicated'
88
+ end
89
+
90
+ it 'should have the replicated attribute as true' do
91
+ expect(proxy.replicated).to be true
92
+ end
93
+
94
+ it 'should initialize the list of shards' do
95
+ expect(proxy.slaves_list).to eq(%w(slave1 slave2 slave3 slave4))
96
+ end
97
+ end
98
+
99
+ describe 'when you have a rails application' do
100
+ before(:each) do
101
+ Rails = double
102
+ OctopusHelper.octopus_env = 'octopus_rails'
103
+ end
104
+
105
+ after(:each) do
106
+ Object.send(:remove_const, :Rails)
107
+ Octopus.instance_variable_set(:@config, nil)
108
+ Octopus.instance_variable_set(:@rails_env, nil)
109
+ OctopusHelper.clean_connection_proxy
110
+ end
111
+
112
+ it 'should initialize correctly octopus common variables for the environments' do
113
+ allow(Rails).to receive(:env).and_return('staging')
114
+ Octopus.instance_variable_set(:@rails_env, nil)
115
+ Octopus.instance_variable_set(:@environments, nil)
116
+ Octopus.config
117
+
118
+ expect(proxy.replicated).to be true
119
+ expect(Octopus.environments).to eq(%w(staging production))
120
+ end
121
+
122
+ it 'should initialize correctly the shards for the staging environment' do
123
+ allow(Rails).to receive(:env).and_return('staging')
124
+ Octopus.instance_variable_set(:@rails_env, nil)
125
+ Octopus.instance_variable_set(:@environments, nil)
126
+ Octopus.config
127
+
128
+ expect(proxy.shards.keys.to_set).to eq(Set.new(%w(slave1 slave2 master)))
129
+ end
130
+
131
+ it 'should initialize correctly the shard octopus_shard value for logging' do
132
+ allow(Rails).to receive(:env).and_return('staging')
133
+ Octopus.instance_variable_set(:@rails_env, nil)
134
+ Octopus.instance_variable_set(:@environments, nil)
135
+ Octopus.config
136
+
137
+ expect(proxy.shards['slave1'].spec.config).to have_key :octopus_shard
138
+ end
139
+
140
+ it 'should initialize correctly the shards for the production environment' do
141
+ allow(Rails).to receive(:env).and_return('production')
142
+ Octopus.instance_variable_set(:@rails_env, nil)
143
+ Octopus.instance_variable_set(:@environments, nil)
144
+ Octopus.config
145
+
146
+ expect(proxy.shards.keys.to_set).to eq(Set.new(%w(slave3 slave4 master)))
147
+ end
148
+
149
+ describe 'using the master connection', :shards => [:russia, :master] do
150
+ before(:each) do
151
+ allow(Rails).to receive(:env).and_return('development')
152
+ end
153
+
154
+ it 'should use the master connection' do
155
+ user = User.create!(:name => 'Thiago')
156
+ user.name = 'New Thiago'
157
+ user.save
158
+ expect(User.find_by_name('New Thiago')).not_to be_nil
159
+ end
160
+
161
+ it 'should work when using using syntax' do
162
+ user = User.using(:russia).create!(:name => 'Thiago')
163
+
164
+ user.name = 'New Thiago'
165
+ user.save
166
+
167
+ expect(User.using(:russia).find_by_name('New Thiago')).to eq(user)
168
+ expect(User.find_by_name('New Thiago')).to eq(user)
169
+ end
170
+
171
+ it 'should work when using blocks' do
172
+ Octopus.using(:russia) do
173
+ @user = User.create!(:name => 'Thiago')
174
+ end
175
+
176
+ expect(User.find_by_name('Thiago')).to eq(@user)
177
+ end
178
+
179
+ it 'should work with associations' do
180
+ u = Client.create!(:name => 'Thiago')
181
+ i = Item.create(:name => 'Item')
182
+ u.items << i
183
+ u.save
184
+ end
185
+ end
186
+ end
187
+
188
+ describe 'returning the correct connection' do
189
+ describe 'should return the shard name' do
190
+ it 'when current_shard is empty' do
191
+ expect(proxy.shard_name).to eq(:master)
192
+ end
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
+
202
+ it 'when current_shard is a single shard' do
203
+ proxy.current_shard = :canada
204
+ expect(proxy.shard_name).to eq(:canada)
205
+ end
206
+
207
+ it 'when current_shard is more than one shard' do
208
+ proxy.current_shard = [:russia, :brazil]
209
+ expect(proxy.shard_name).to eq(:russia)
210
+ end
211
+ end
212
+
213
+ describe 'should return the connection based on shard_name' do
214
+ it 'when current_shard is empty' do
215
+ expect(proxy.select_connection).to eq(proxy.shards[:master].connection)
216
+ end
217
+
218
+ it 'when current_shard is a single shard' do
219
+ proxy.current_shard = :canada
220
+ expect(proxy.select_connection).to eq(proxy.shards[:canada].connection)
221
+ end
222
+ end
223
+ end
224
+
225
+ describe 'saving multiple sharded objects at once' do
226
+ before :each do
227
+ @p = MmorpgPlayer.using(:alone_shard).create!(:player_name => 'Thiago')
228
+ end
229
+
230
+ subject { @p.save! }
231
+
232
+ context 'when the objects are created with #new and saved one at a time' do
233
+ before :each do
234
+ @p.weapons.create!(:name => 'battleaxe', :hand => 'right')
235
+ @p.skills.create!(:name => 'smiting', :weapon => @p.weapons[0])
236
+ end
237
+
238
+ it 'should save all associated objects on the correct shard' do
239
+ expect { subject }.to_not raise_error
240
+ end
241
+ end
242
+
243
+ context 'when the objects are created with #new and saved at the same time' do
244
+ before :each do
245
+ @p.weapons.new(:name => 'battleaxe', :hand => 'right')
246
+ @p.skills.new(:name => 'smiting', :weapon => @p.weapons[0])
247
+ end
248
+
249
+ it 'should save all associated objects on the correct shard' do
250
+ expect { subject }.to_not raise_error
251
+ end
252
+ end
253
+ end
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
+
271
+ describe 'connection reuse' do
272
+ before :each do
273
+ @item_brazil_conn = Item.using(:brazil).new(:name => 'Brazil Item').class.connection.select_connection
274
+ @item_canada_conn = Item.using(:canada).new(:name => 'Canada Item').class.connection.select_connection
275
+ end
276
+
277
+ it 'reuses connections' do
278
+ expect(Item.using(:brazil).new(:name => 'Another Brazil Item').class.connection.select_connection).to eq(@item_brazil_conn)
279
+ expect(Item.using(:canada).new(:name => 'Another Canada Item').class.connection.select_connection).to eq(@item_canada_conn)
280
+ end
281
+
282
+ it 'reuses connections after clear_active_connections! is called' do
283
+ expect(Item.using(:brazil).new(:name => 'Another Brazil Item').class.connection.select_connection).to eq(@item_brazil_conn)
284
+ expect(Item.using(:canada).new(:name => 'Another Canada Item').class.connection.select_connection).to eq(@item_canada_conn)
285
+ end
286
+
287
+ it 'creates new connections after clear_all_connections! is called' do
288
+ Item.clear_all_connections!
289
+ expect(Item.using(:brazil).new(:name => 'Another Brazil Item').class.connection.select_connection).not_to eq(@item_brazil_conn)
290
+ expect(Item.using(:canada).new(:name => 'Another Canada Item').class.connection.select_connection).not_to eq(@item_canada_conn)
291
+ end
292
+
293
+ it 'is consistent with connected?' do
294
+ expect(Item.connected?).to be true
295
+ expect(ActiveRecord::Base.connected?).to be true
296
+
297
+ Item.clear_all_connections!
298
+
299
+ expect(Item.connected?).to be false
300
+ expect(ActiveRecord::Base.connected?).to be false
301
+ end
302
+ end
303
+ end
@@ -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
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ describe Octopus::RelationProxy do
4
+ describe 'shard tracking' do
5
+ before :each do
6
+ @client = Client.using(:canada).create!
7
+ @client.items << Item.using(:canada).create!
8
+ @relation = @client.items
9
+ end
10
+
11
+ it 'remembers the shard on which a relation was created' do
12
+ expect(@relation.current_shard).to eq(:canada)
13
+ end
14
+
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
48
+ end
49
+ end
50
+
51
+ context 'when comparing to other Relation objects' do
52
+ before :each do
53
+ @relation.reset
54
+ end
55
+
56
+ it 'is equal to its clone' do
57
+ expect(@relation).to eq(@relation.clone)
58
+ end
59
+ end
60
+
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
64
+
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
68
+
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
72
+
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)
91
+ end
92
+ end
93
+
94
+ context 'when no explicit shard context is provided' do
95
+ it 'uses the correct shard' do
96
+ expect(@relation.count).to eq(1)
97
+ end
98
+
99
+ it 'lazily evaluates on the correct shard' do
100
+ # Do something to force Client.connection_proxy.current_shard to change
101
+ _some_count = Client.using(:brazil).count
102
+ expect(@relation.select(:client_id).count).to eq(1)
103
+ end
104
+ end
105
+
106
+ context 'when an explicit, but different, shard context is provided' do
107
+ it 'uses the correct shard' do
108
+ expect(Item.using(:brazil).count).to eq(0)
109
+ _clients_on_brazil = Client.using(:brazil).all
110
+ Octopus.using(:brazil) do
111
+ expect(@relation.count).to eq(1)
112
+ end
113
+ end
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
+
124
+ it 'lazily evaluates on the correct shard' do
125
+ expect(Item.using(:brazil).count).to eq(0)
126
+ Octopus.using(:brazil) do
127
+ expect(@relation.select(:client_id).count).to eq(1)
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'when the database is replicated and has slave groups' do
4
+
5
+ it 'should pick the slave group based on current_slave_grup when you have a replicated model' do
6
+
7
+ OctopusHelper.using_environment :replicated_slave_grouped do
8
+ # The following two calls of `create!` both creates cats in :master(The database `octopus_shard_1`)
9
+ # which is configured through RAILS_ENV and database.yml
10
+ Cat.create!(:name => 'Thiago1')
11
+ Cat.create!(:name => 'Thiago2')
12
+
13
+ # See "replicated_slave_grouped" defined in shards.yml
14
+ # We have:
15
+ # The database `octopus_shard_1` as :slave21 which is a member of the slave group :slaves2, and as :master
16
+ # The databse `octopus_shard_2` as :slave11 which is a member of the slave group :slaves1
17
+ # When a select-count query is sent to `octopus_shard_1`, it should return 2 because we have create two cats in :master .
18
+ # When a select-count query is sent to `octopus_shard_2`, it should return 0.
19
+
20
+ # The query goes to `octopus_shard_1`
21
+ expect(Cat.using(:master).count).to eq(2)
22
+ # The query goes to `octopus_shard_1`
23
+ expect(Cat.count).to eq(2)
24
+ # The query goes to `octopus_shard_2`
25
+ expect(Cat.using(:slave_group => :slaves1).count).to eq(0)
26
+ # The query goes to `octopus_shard_1`
27
+ expect(Cat.using(:slave_group => :slaves2).count).to eq(2)
28
+ end
29
+ end
30
+
31
+ it 'should distribute queries between slaves in a slave group in round-robin' do
32
+ OctopusHelper.using_environment :replicated_slave_grouped do
33
+ # The query goes to :master(`octopus_shard_1`)
34
+ Cat.create!(:name => 'Thiago1')
35
+ # The query goes to :master(`octopus_shard_1`)
36
+ Cat.create!(:name => 'Thiago2')
37
+
38
+ # The query goes to :slave32(`octopus_shard_2`)
39
+ expect(Cat.using(:slave_group => :slaves3).count).to eq(0)
40
+ # The query goes to :slave31(`octopus_shard_1`)
41
+ expect(Cat.using(:slave_group => :slaves3).count).to eq(2)
42
+ # The query goes to :slave32(`octopus_shard_2`)
43
+ expect(Cat.using(:slave_group => :slaves3).count).to eq(0)
44
+ end
45
+ end
46
+
47
+ it 'should make queries to master when slave groups are configured but not selected' do
48
+ OctopusHelper.using_environment :replicated_slave_grouped do
49
+ # All the queries go to :master(`octopus_shard_1`)
50
+
51
+ Cat.create!(:name => 'Thiago1')
52
+ Cat.create!(:name => 'Thiago2')
53
+
54
+ # In `database.yml` and `shards.yml`, we have configured 1 master and 4 slaves.
55
+ # So we can ensure Octopus is not distributing queries between them
56
+ # by asserting 1 + 4 = 5 queries go to :master(`octopus_shard_1`)
57
+ expect(Cat.count).to eq(2)
58
+ expect(Cat.count).to eq(2)
59
+ expect(Cat.count).to eq(2)
60
+ expect(Cat.count).to eq(2)
61
+ expect(Cat.count).to eq(2)
62
+ end
63
+ end
64
+
65
+ it 'should keep sending to slaves in a using block' do
66
+ OctopusHelper.using_environment :replicated_slave_grouped do
67
+ Cat.create!(:name => 'Thiago1')
68
+ Cat.create!(:name => 'Thiago2')
69
+
70
+ expect(Cat.count).to eq(2)
71
+ Octopus.using(:slave_group => :slaves1) do
72
+ expect(Cat.count).to eq(0)
73
+ expect(Cat.count).to eq(0)
74
+ end
75
+ end
76
+ end
77
+
78
+ it 'should restore previous slave group after a using block' do
79
+ OctopusHelper.using_environment :replicated_slave_grouped do
80
+ Cat.create!(:name => 'Thiago1')
81
+ Cat.create!(:name => 'Thiago2')
82
+
83
+ Octopus.using(:slave_group => :slaves1) do
84
+ Octopus.using(:slave_group => :slaves2) do
85
+ expect(Cat.count).to eq(2)
86
+ end
87
+ expect(Cat.count).to eq(0)
88
+ end
89
+ end
90
+ end
91
+ end