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