ar-octopus-ruby-3 0.11.2

Sign up to get free protection for your applications and to get access to all the features.
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,837 @@
1
+ require 'spec_helper'
2
+
3
+ describe Octopus::Model do
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
+
9
+ it 'should allow to send a block to the master shard' do
10
+ Octopus.using(:master) do
11
+ User.create!(:name => 'Block test')
12
+ end
13
+
14
+ expect(User.using(:master).find_by_name('Block test')).not_to be_nil
15
+ end
16
+
17
+ it 'should allow to pass a string as the shard name to a AR subclass' do
18
+ User.using('canada').create!(:name => 'Rafael Pilha')
19
+
20
+ expect(User.using('canada').find_by_name('Rafael Pilha')).not_to be_nil
21
+ end
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
+
33
+ it 'should allow to pass a string as the shard name to a block' do
34
+ Octopus.using('canada') do
35
+ User.create!(:name => 'Rafael Pilha')
36
+ end
37
+
38
+ expect(User.using('canada').find_by_name('Rafael Pilha')).not_to be_nil
39
+ end
40
+
41
+ it 'should allow selecting the shards on scope' do
42
+ User.using(:canada).create!(:name => 'oi')
43
+ expect(User.using(:canada).count).to eq(1)
44
+ expect(User.count).to eq(0)
45
+ end
46
+
47
+ it 'should allow selecting the shard using #new' do
48
+ u = User.using(:canada).new
49
+ u.name = 'Thiago'
50
+ u.save
51
+
52
+ expect(User.using(:canada).count).to eq(1)
53
+ expect(User.using(:brazil).count).to eq(0)
54
+
55
+ u1 = User.new
56
+ u1.name = 'Joaquim'
57
+ u2 = User.using(:canada).new
58
+ u2.name = 'Manuel'
59
+ u1.save
60
+ u2.save
61
+
62
+ expect(User.using(:canada).all).to eq([u, u2])
63
+ expect(User.all).to eq([u1])
64
+ end
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
+
88
+ describe 'multiple calls to the same scope' do
89
+ it 'works with nil response' do
90
+ scope = User.using(:canada)
91
+ expect(scope.count).to eq(0)
92
+ expect(scope.first).to be_nil
93
+ end
94
+
95
+ it 'works with non-nil response' do
96
+ user = User.using(:canada).create!(:name => 'oi')
97
+ scope = User.using(:canada)
98
+ expect(scope.count).to eq(1)
99
+ expect(scope.first).to eq(user)
100
+ end
101
+ end
102
+
103
+ it 'should select the correct shard' do
104
+ User.using(:canada)
105
+ User.create!(:name => 'oi')
106
+ expect(User.count).to eq(1)
107
+ end
108
+
109
+ it 'should ensure that the connection will be cleaned' do
110
+ expect(ActiveRecord::Base.connection.current_shard).to eq(:master)
111
+ expect do
112
+ Octopus.using(:canada) do
113
+ fail 'Some Exception'
114
+ end
115
+ end.to raise_error(RuntimeError)
116
+
117
+ expect(ActiveRecord::Base.connection.current_shard).to eq(:master)
118
+ end
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
+
135
+ it 'should allow creating more than one user' do
136
+ User.using(:canada).create([{ :name => 'America User 1' }, { :name => 'America User 2' }])
137
+ User.create!(:name => 'Thiago')
138
+ expect(User.using(:canada).find_by_name('America User 1')).not_to be_nil
139
+ expect(User.using(:canada).find_by_name('America User 2')).not_to be_nil
140
+ expect(User.using(:master).find_by_name('Thiago')).not_to be_nil
141
+ end
142
+
143
+ it 'should work when you have a SQLite3 shard' do
144
+ u = User.using(:sqlite_shard).create!(:name => 'Sqlite3')
145
+ expect(User.using(:sqlite_shard).where(name: 'Sqlite3').first).to eq(u)
146
+ end
147
+
148
+ it 'should clean #current_shard from proxy when using execute' do
149
+ User.using(:canada).connection.execute('select * from users limit 1;')
150
+ expect(User.connection.current_shard).to eq(:master)
151
+ end
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
+
162
+ it 'should allow scoping dynamically' do
163
+ User.using(:canada).using(:master).using(:canada).create!(:name => 'oi')
164
+ expect(User.using(:canada).using(:master).count).to eq(0)
165
+ expect(User.using(:master).using(:canada).count).to eq(1)
166
+ end
167
+
168
+ it 'should allow find inside blocks' do
169
+ @user = User.using(:brazil).create!(:name => 'Thiago')
170
+
171
+ Octopus.using(:brazil) do
172
+ expect(User.first).to eq(@user)
173
+ end
174
+
175
+ expect(User.using(:brazil).find_by_name('Thiago')).to eq(@user)
176
+ end
177
+
178
+ it 'should clean the current_shard after executing the current query' do
179
+ User.using(:canada).create!(:name => 'oi')
180
+ expect(User.count).to eq(0)
181
+ end
182
+
183
+ it 'should support both groups and alone shards' do
184
+ _u = User.using(:alone_shard).create!(:name => 'Alone')
185
+ expect(User.using(:alone_shard).count).to eq(1)
186
+ expect(User.using(:canada).count).to eq(0)
187
+ expect(User.using(:brazil).count).to eq(0)
188
+ expect(User.count).to eq(0)
189
+ end
190
+
191
+ it 'should work with named scopes' do
192
+ u = User.using(:brazil).create!(:name => 'Thiago')
193
+
194
+ expect(User.thiago.using(:brazil).first).to eq(u)
195
+ expect(User.using(:brazil).thiago.first).to eq(u)
196
+
197
+ Octopus.using(:brazil) do
198
+ expect(User.thiago.first).to eq(u)
199
+ end
200
+ end
201
+
202
+ describe '#current_shard attribute' do
203
+ it 'should store the attribute when you create or find an object' do
204
+ u = User.using(:alone_shard).create!(:name => 'Alone')
205
+ expect(u.current_shard).to eq(:alone_shard)
206
+ User.using(:canada).create!(:name => 'oi')
207
+ u = User.using(:canada).find_by_name('oi')
208
+ expect(u.current_shard).to eq(:canada)
209
+ end
210
+
211
+ it 'should store the attribute when you find multiple instances' do
212
+ 5.times { User.using(:alone_shard).create!(:name => 'Alone') }
213
+
214
+ User.using(:alone_shard).all.each do |u|
215
+ expect(u.current_shard).to eq(:alone_shard)
216
+ end
217
+ end
218
+
219
+ it 'should works when you find, and after that, alter that object' do
220
+ alone_user = User.using(:alone_shard).create!(:name => 'Alone')
221
+ _mstr_user = User.using(:master).create!(:name => 'Master')
222
+ alone_user.name = 'teste'
223
+ alone_user.save
224
+ expect(User.using(:master).first.name).to eq('Master')
225
+ expect(User.using(:alone_shard).first.name).to eq('teste')
226
+ end
227
+
228
+ it 'should work for the reload method' do
229
+ User.using(:alone_shard).create!(:name => 'Alone')
230
+ u = User.using(:alone_shard).find_by_name('Alone')
231
+ u.reload
232
+ expect(u.name).to eq('Alone')
233
+ end
234
+
235
+ it 'should work passing some arguments to reload method' do
236
+ User.using(:alone_shard).create!(:name => 'Alone')
237
+ u = User.using(:alone_shard).find_by_name('Alone')
238
+ u.reload(:lock => true)
239
+ expect(u.name).to eq('Alone')
240
+ end
241
+ end
242
+
243
+ describe 'passing a block' do
244
+ it 'should allow queries be executed inside the block, ponting to a specific shard' do
245
+ Octopus.using(:canada) do
246
+ User.create(:name => 'oi')
247
+ end
248
+
249
+ expect(User.using(:canada).count).to eq(1)
250
+ expect(User.using(:master).count).to eq(0)
251
+ expect(User.count).to eq(0)
252
+ end
253
+
254
+ it 'should allow execute queries inside a model' do
255
+ u = User.new
256
+ u.awesome_queries
257
+ expect(User.using(:canada).count).to eq(1)
258
+ expect(User.count).to eq(0)
259
+ end
260
+ end
261
+
262
+ describe 'raising errors' do
263
+ it "should raise a error when you specify a shard that doesn't exist" do
264
+ expect { User.using(:crazy_shard).create!(:name => 'Thiago') }.to raise_error('Nonexistent Shard Name: crazy_shard')
265
+ end
266
+ end
267
+
268
+ describe 'equality' do
269
+ let(:canada1) do
270
+ u = User.new
271
+ u.id = 1
272
+ u.current_shard = :canada
273
+ u
274
+ end
275
+
276
+ let(:canada1_dup) do
277
+ u = User.new
278
+ u.id = 1
279
+ u.current_shard = :canada
280
+ u
281
+ end
282
+
283
+ let(:brazil1) do
284
+ u = User.new
285
+ u.id = 1
286
+ u.current_shard = :brazil
287
+ u
288
+ end
289
+
290
+ it 'should work with persisted objects' do
291
+ u = User.using(:brazil).create(:name => 'Mike')
292
+ expect(User.using(:brazil).find_by_name('Mike')).to eq(u)
293
+ end
294
+
295
+ it 'should check current_shard when determining equality' do
296
+ expect(canada1).not_to eq(brazil1)
297
+ expect(canada1).to eq(canada1_dup)
298
+ end
299
+
300
+ it 'delegates equality check on scopes' do
301
+ u = User.using(:brazil).create!(:name => 'Mike')
302
+ expect(User.using(:brazil).where(:name => 'Mike')).to eq([u])
303
+ end
304
+ end
305
+ end
306
+
307
+ describe 'using a postgresql shard' do
308
+ it 'should update the Arel Engine' do
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
316
+ end
317
+
318
+ it 'should works with writes and reads' do
319
+ u = User.using(:postgresql_shard).create!(:name => 'PostgreSQL User')
320
+ expect(User.using(:postgresql_shard).all).to eq([u])
321
+ expect(User.using(:alone_shard).all).to eq([])
322
+ end
323
+ end
324
+
325
+ describe 'AR basic methods' do
326
+ it 'establish_connection' do
327
+ expect(CustomConnection.connection.current_database).to eq('octopus_shard_2')
328
+ end
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
+
336
+ it 'should not mess with custom connection table names' do
337
+ expect(Advert.connection.current_database).to eq('octopus_shard_1')
338
+ Advert.create!(:name => 'Teste')
339
+ end
340
+
341
+ it 'increment' do
342
+ _ = User.using(:brazil).create!(:name => 'Teste', :number => 10)
343
+ u = User.using(:brazil).find_by_number(10)
344
+ u.increment(:number)
345
+ u.save
346
+ expect(User.using(:brazil).find_by_number(11)).not_to be_nil
347
+ end
348
+
349
+ it 'increment!' do
350
+ _ = User.using(:brazil).create!(:name => 'Teste', :number => 10)
351
+ u = User.using(:brazil).find_by_number(10)
352
+ u.increment!(:number)
353
+ expect(User.using(:brazil).find_by_number(11)).not_to be_nil
354
+ end
355
+
356
+ it 'decrement' do
357
+ _ = User.using(:brazil).create!(:name => 'Teste', :number => 10)
358
+ u = User.using(:brazil).find_by_number(10)
359
+ u.decrement(:number)
360
+ u.save
361
+ expect(User.using(:brazil).find_by_number(9)).not_to be_nil
362
+ end
363
+
364
+ it 'decrement!' do
365
+ _ = User.using(:brazil).create!(:name => 'Teste', :number => 10)
366
+ u = User.using(:brazil).find_by_number(10)
367
+ u.decrement!(:number)
368
+ expect(User.using(:brazil).find_by_number(9)).not_to be_nil
369
+ end
370
+
371
+ it 'toggle' do
372
+ _ = User.using(:brazil).create!(:name => 'Teste', :admin => false)
373
+ u = User.using(:brazil).find_by_name('Teste')
374
+ u.toggle(:admin)
375
+ u.save
376
+ expect(User.using(:brazil).find_by_name('Teste').admin).to be true
377
+ end
378
+
379
+ it 'toggle!' do
380
+ _ = User.using(:brazil).create!(:name => 'Teste', :admin => false)
381
+ u = User.using(:brazil).find_by_name('Teste')
382
+ u.toggle!(:admin)
383
+ expect(User.using(:brazil).find_by_name('Teste').admin).to be true
384
+ end
385
+
386
+ it 'count' do
387
+ _u = User.using(:brazil).create!(:name => 'User1')
388
+ _v = User.using(:brazil).create!(:name => 'User2')
389
+ _w = User.using(:brazil).create!(:name => 'User3')
390
+ expect(User.using(:brazil).where(:name => 'User2').all.count).to eq(1)
391
+ end
392
+
393
+ it 'maximum' do
394
+ _u = User.using(:brazil).create!(:name => 'Teste', :number => 11)
395
+ _v = User.using(:master).create!(:name => 'Teste', :number => 12)
396
+
397
+ expect(User.using(:brazil).maximum(:number)).to eq(11)
398
+ expect(User.using(:master).maximum(:number)).to eq(12)
399
+ end
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
+
413
+ describe 'any?' do
414
+ before { User.using(:brazil).create!(:name => 'User1') }
415
+
416
+ it 'works when true' do
417
+ scope = User.using(:brazil).where(:name => 'User1')
418
+ expect(scope.any?).to be true
419
+ end
420
+
421
+ it 'works when false' do
422
+ scope = User.using(:brazil).where(:name => 'User2')
423
+ expect(scope.any?).to be false
424
+ end
425
+ end
426
+
427
+ it 'exists?' do
428
+ @user = User.using(:brazil).create!(:name => 'User1')
429
+
430
+ expect(User.using(:brazil).where(:name => 'User1').exists?).to be true
431
+ expect(User.using(:brazil).where(:name => 'User2').exists?).to be false
432
+ end
433
+
434
+ describe 'touch' do
435
+ it 'updates updated_at by default' do
436
+ @user = User.using(:brazil).create!(:name => 'User1')
437
+ User.using(:brazil).where(:id => @user.id).update_all(:updated_at => Time.now - 3.months)
438
+ @user.touch
439
+ expect(@user.reload.updated_at.in_time_zone('GMT').to_date).to eq(Time.now.in_time_zone('GMT').to_date)
440
+ end
441
+
442
+ it 'updates passed in attribute name' do
443
+ @user = User.using(:brazil).create!(:name => 'User1')
444
+ User.using(:brazil).where(:id => @user.id).update_all(:created_at => Time.now - 3.months)
445
+ @user.touch(:created_at)
446
+ expect(@user.reload.created_at.in_time_zone('GMT').to_date).to eq(Time.now.in_time_zone('GMT').to_date)
447
+ end
448
+ end
449
+
450
+ describe '#pluck' do
451
+ before { User.using(:brazil).create!(:name => 'User1') }
452
+
453
+ it 'should works from scope proxy' do
454
+ names = User.using(:brazil).pluck(:name)
455
+ expect(names).to eq(['User1'])
456
+ expect(User.using(:master).pluck(:name)).to eq([])
457
+ end
458
+ end
459
+
460
+ it 'update_column' do
461
+ @user = User.using(:brazil).create!(:name => 'User1')
462
+ @user2 = User.using(:brazil).find(@user.id)
463
+ @user2.update_column(:name, 'Joaquim Shard Brazil')
464
+ expect(User.using(:brazil).find_by_name('Joaquim Shard Brazil')).not_to be_nil
465
+ end
466
+
467
+ it 'update_attributes' do
468
+ @user = User.using(:brazil).create!(:name => 'User1')
469
+ @user2 = User.using(:brazil).find(@user.id)
470
+ @user2.update_attributes(:name => 'Joaquim')
471
+ expect(User.using(:brazil).find_by_name('Joaquim')).not_to be_nil
472
+ end
473
+
474
+ it 'using update_attributes inside a block' do
475
+ Octopus.using(:brazil) do
476
+ @user = User.create!(:name => 'User1')
477
+ @user2 = User.find(@user.id)
478
+ @user2.update_attributes(:name => 'Joaquim')
479
+ end
480
+
481
+ expect(User.find_by_name('Joaquim')).to be_nil
482
+ expect(User.using(:brazil).find_by_name('Joaquim')).not_to be_nil
483
+ end
484
+
485
+ it 'update_attribute' do
486
+ @user = User.using(:brazil).create!(:name => 'User1')
487
+ @user2 = User.using(:brazil).find(@user.id)
488
+ @user2.update_attribute(:name, 'Joaquim')
489
+ expect(User.using(:brazil).find_by_name('Joaquim')).not_to be_nil
490
+ end
491
+
492
+ it 'as_json' do
493
+ ActiveRecord::Base.include_root_in_json = false
494
+
495
+ Octopus.using(:brazil) do
496
+ User.create!(:name => 'User1')
497
+ end
498
+
499
+ user = User.using(:brazil).where(:name => 'User1').first
500
+ expect(user.as_json(:except => [:created_at, :updated_at, :id, :current_shard])).to eq('admin' => nil, 'name' => 'User1', 'number' => nil)
501
+ end
502
+
503
+ describe 'transaction' do
504
+ context 'without assigning a database' do
505
+ it 'works as expected' do
506
+ _u = User.create!(:name => 'Thiago')
507
+
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
515
+
516
+ expect(User.using(:brazil).count).to eq(1)
517
+ expect(User.using(:master).count).to eq(1)
518
+ end
519
+ end
520
+
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
610
+ end
611
+
612
+ describe 'deleting a record' do
613
+ before(:each) do
614
+ @user = User.using(:brazil).create!(:name => 'User1')
615
+ @user2 = User.using(:brazil).find(@user.id)
616
+ end
617
+
618
+ it 'delete' do
619
+ @user2.delete
620
+ expect { User.using(:brazil).find(@user2.id) }.to raise_error(ActiveRecord::RecordNotFound)
621
+ end
622
+
623
+ it "delete within block shouldn't lose shard" do
624
+ Octopus.using(:brazil) do
625
+ @user2.delete
626
+ @user3 = User.create(:name => 'User3')
627
+
628
+ expect(User.connection.current_shard).to eq(:brazil)
629
+ expect(User.find(@user3.id)).to eq(@user3)
630
+ end
631
+ end
632
+
633
+ it 'destroy' do
634
+ @user2.destroy
635
+ expect { User.using(:brazil).find(@user2.id) }.to raise_error(ActiveRecord::RecordNotFound)
636
+ end
637
+
638
+ it "destroy within block shouldn't lose shard" do
639
+ Octopus.using(:brazil) do
640
+ @user2.destroy
641
+ @user3 = User.create(:name => 'User3')
642
+
643
+ expect(User.connection.current_shard).to eq(:brazil)
644
+ expect(User.find(@user3.id)).to eq(@user3)
645
+ end
646
+ end
647
+ end
648
+ end
649
+
650
+ describe 'custom connection' do
651
+ context 'by default' do
652
+ it 'with plain call should use custom connection' do
653
+ expect(CustomConnection.connection.current_database).to eq('octopus_shard_2')
654
+ end
655
+
656
+ it 'should ignore using called on relation' do
657
+ expect(CustomConnection.using(:postgresql_shard).connection.current_database).to eq('octopus_shard_2')
658
+ end
659
+
660
+ it 'should ignore Octopus.using block' do
661
+ Octopus.using(:postgresql_shard) do
662
+ expect(CustomConnection.connection.current_database).to eq('octopus_shard_2')
663
+ end
664
+ end
665
+
666
+ it 'should save to correct shard' do
667
+ expect { CustomConnection.create(:value => 'custom value') }.to change {
668
+ CustomConnection
669
+ .connection
670
+ .execute("select count(*) as ct from custom where value = 'custom value'")
671
+ .to_a.first.first
672
+ }.by 1
673
+ end
674
+ end
675
+
676
+ context 'with allowed_shards configured' do
677
+ before do
678
+ CustomConnection.allow_shard :postgresql_shard
679
+ end
680
+
681
+ it 'with plain call should use custom connection' do
682
+ expect(CustomConnection.connection.current_database).to eq('octopus_shard_2')
683
+ end
684
+
685
+ it 'with using called on relation with allowed shard should use' do
686
+ expect(CustomConnection.using(:postgresql_shard).connection.current_database).to eq('octopus_shard_1')
687
+ end
688
+
689
+ it 'within Octopus.using block with allowed shard should use' do
690
+ Octopus.using(:postgresql_shard) do
691
+ expect(CustomConnection.connection.current_database).to eq('octopus_shard_1')
692
+ end
693
+ end
694
+
695
+ it 'with using called on relation with disallowed shard should not use' do
696
+ expect(CustomConnection.using(:brazil).connection.current_database).to eq('octopus_shard_2')
697
+ end
698
+
699
+ it 'within Octopus.using block with disallowed shard should not use' do
700
+ Octopus.using(:brazil) do
701
+ expect(CustomConnection.connection.current_database).to eq('octopus_shard_2')
702
+ end
703
+ end
704
+
705
+ it 'should save to correct shard' do
706
+ expect { CustomConnection.create(:value => 'custom value') }.to change {
707
+ CustomConnection
708
+ .connection
709
+ .execute("select count(*) as ct from custom where value = 'custom value'")
710
+ .to_a.first.first
711
+ }.by 1
712
+ end
713
+
714
+ it 'should clean up correctly' do
715
+ User.create!(:name => 'CleanUser')
716
+ CustomConnection.using(:postgresql_shard).first
717
+ expect(User.first).not_to be_nil
718
+ end
719
+
720
+ it 'should clean up correctly even inside block' do
721
+ User.create!(:name => 'CleanUser')
722
+
723
+ Octopus.using(:master) do
724
+ CustomConnection.using(:postgresql_shard).connection.execute('select count(*) from users')
725
+ expect(User.first).not_to be_nil
726
+ end
727
+ end
728
+ end
729
+
730
+ describe 'clear_active_connections!' do
731
+ it 'should not leak connection' do
732
+ CustomConnection.create(:value => 'custom value')
733
+
734
+ # This is what Rails, Sidekiq etc call--this normally handles all connection pools in the app
735
+ expect { ActiveRecord::Base.clear_active_connections! }
736
+ .to change { CustomConnection.connection_pool.active_connection? }
737
+
738
+ expect(CustomConnection.connection_pool.active_connection?).to be_falsey
739
+ end
740
+ end
741
+ end
742
+
743
+ describe 'when using set_table_name' do
744
+ it 'should work correctly' do
745
+ Bacon.using(:brazil).create!(:name => 'YUMMMYYYY')
746
+ end
747
+
748
+ it 'should work correctly with a block' do
749
+ Cheese.using(:brazil).create!(:name => 'YUMMMYYYY')
750
+ end
751
+ end
752
+
753
+ describe 'when using table_name=' do
754
+ it 'should work correctly' do
755
+ Ham.using(:brazil).create!(:name => 'YUMMMYYYY')
756
+ end
757
+ end
758
+
759
+ describe 'when using a environment with a single adapter' do
760
+ it 'should not clean the table name' do
761
+ OctopusHelper.using_environment :production_fully_replicated do
762
+ expect(Keyboard).not_to receive(:reset_table_name)
763
+ Keyboard.using(:master).create!(:name => 'Master Cat')
764
+ end
765
+ end
766
+ end
767
+
768
+ describe 'when you have joins/include' do
769
+ before(:each) do
770
+ @client1 = Client.using(:brazil).create(:name => 'Thiago')
771
+
772
+ Octopus.using(:canada) do
773
+ @client2 = Client.create(:name => 'Mike')
774
+ @client3 = Client.create(:name => 'Joao')
775
+ @item1 = Item.create(:client => @client2, :name => 'Item 1')
776
+ @item2 = Item.create(:client => @client2, :name => 'Item 2')
777
+ @item3 = Item.create(:client => @client3, :name => 'Item 3')
778
+ @part1 = Part.create(:item => @item1, :name => 'Part 1')
779
+ @part2 = Part.create(:item => @item1, :name => 'Part 2')
780
+ @part3 = Part.create(:item => @item2, :name => 'Part 3')
781
+ end
782
+
783
+ @item4 = Item.using(:brazil).create(:client => @client1, :name => 'Item 4')
784
+ end
785
+
786
+ it 'should work using the rails 3.x syntax' do
787
+ items = Item.using(:canada).joins(:client).where("clients.id = #{@client2.id}").all
788
+ expect(items).to eq([@item1, @item2])
789
+ end
790
+
791
+ it 'should work for include also, rails 3.x syntax' do
792
+ items = Item.using(:canada).includes(:client).where(:clients => { :id => @client2.id }).all
793
+ expect(items).to eq([@item1, @item2])
794
+ end
795
+ end
796
+
797
+ describe 'ActiveRecord::Base Validations' do
798
+ it 'should work correctly when using validations' do
799
+ @key = Keyboard.create!(:name => 'Key')
800
+ expect { Keyboard.using(:brazil).create!(:name => 'Key') }.not_to raise_error
801
+ expect { Keyboard.create!(:name => 'Key') }.to raise_error(ActiveRecord::RecordInvalid)
802
+ end
803
+
804
+ it 'should work correctly when using validations with using syntax' do
805
+ @key = Keyboard.using(:brazil).create!(:name => 'Key')
806
+ expect { Keyboard.create!(:name => 'Key') }.not_to raise_error
807
+ expect { Keyboard.using(:brazil).create!(:name => 'Key') }
808
+ .to raise_error(ActiveRecord::RecordInvalid)
809
+ end
810
+ end
811
+
812
+ describe '#replicated_model method' do
813
+ it 'should be replicated' do
814
+ OctopusHelper.using_environment :production_replicated do
815
+ expect(ActiveRecord::Base.connection_proxy.replicated).to be true
816
+ end
817
+ end
818
+
819
+ it 'should mark the Cat model as replicated' do
820
+ OctopusHelper.using_environment :production_replicated do
821
+ expect(User.replicated).to be_falsey
822
+ expect(Cat.replicated).to be true
823
+ end
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
836
+ end
837
+ end