octoball 0.1.0
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.
- checksums.yaml +7 -0
- data/.circleci/config.yml +46 -0
- data/.gitignore +7 -0
- data/Gemfile +3 -0
- data/README.md +107 -0
- data/Rakefile +54 -0
- data/lib/octoball.rb +30 -0
- data/lib/octoball/association.rb +61 -0
- data/lib/octoball/association_shard_check.rb +44 -0
- data/lib/octoball/connection_adapters.rb +18 -0
- data/lib/octoball/connection_handling.rb +18 -0
- data/lib/octoball/current_shard_tracker.rb +30 -0
- data/lib/octoball/log_subscriber.rb +21 -0
- data/lib/octoball/persistence.rb +29 -0
- data/lib/octoball/relation_proxy.rb +99 -0
- data/lib/octoball/version.rb +5 -0
- data/octoball.gemspec +28 -0
- data/spec/migration/1_test_tables.rb +84 -0
- data/spec/migration/2_alone_shard_tables.rb +19 -0
- data/spec/models/application_record.rb +13 -0
- data/spec/octoball/association_shard_tracking_spec.rb +1024 -0
- data/spec/octoball/collection_proxy_spec.rb +17 -0
- data/spec/octoball/log_subscriber_spec.rb +19 -0
- data/spec/octoball/model_spec.rb +688 -0
- data/spec/octoball/relation_proxy_spec.rb +130 -0
- data/spec/octoball/scope_proxy_spec.rb +97 -0
- data/spec/rails_helper.rb +0 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/database_connection.rb +22 -0
- data/spec/support/database_models.rb +115 -0
- data/spec/support/query_count.rb +17 -0
- data/spec/support/shared_contexts.rb +18 -0
- data/spec/support/test_helper.rb +14 -0
- metadata +174 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Octoball do
|
4
|
+
describe 'method dispatch' do
|
5
|
+
before :each do
|
6
|
+
@client = Client.using(:canada).create!
|
7
|
+
@client.items << Item.using(:canada).create!
|
8
|
+
@client.reload
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'computes the size of the collection without loading it' do
|
12
|
+
expect(@client.items.size).to eq(1)
|
13
|
+
|
14
|
+
expect(@client.items.loaded?).to be false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Octoball::LogSubscriber, :shards => [:canada] do
|
4
|
+
before :each do
|
5
|
+
@out = StringIO.new
|
6
|
+
@log = Logger.new(@out)
|
7
|
+
ActiveRecord::Base.logger = @log
|
8
|
+
ActiveRecord::Base.logger.level = Logger::DEBUG
|
9
|
+
end
|
10
|
+
|
11
|
+
after :each do
|
12
|
+
ActiveRecord::Base.logger = Logger.new(File.open('database.log', 'a'))
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should add to the default logger the shard name the query was sent to' do
|
16
|
+
User.using(:canada).create!(:name => 'test')
|
17
|
+
expect(@out.string).to match(/Shard: canada/)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,688 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Octoball do
|
4
|
+
describe '#using method' do
|
5
|
+
it 'should allow to send a block to the master shard' do
|
6
|
+
Octoball.using(:master) do
|
7
|
+
User.create!(:name => 'Block test')
|
8
|
+
end
|
9
|
+
|
10
|
+
expect(User.using(:master).find_by_name('Block test')).not_to be_nil
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should allow to pass a string as the shard name to a AR subclass' do
|
14
|
+
User.using('canada').create!(:name => 'Rafael Pilha')
|
15
|
+
|
16
|
+
expect(User.using('canada').find_by_name('Rafael Pilha')).not_to be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should allow comparison of a string shard name with symbol shard name' do
|
20
|
+
u = User.using('canada').create!(:name => 'Rafael Pilha')
|
21
|
+
expect(u).to eq(User.using(:canada).find_by_name('Rafael Pilha'))
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should allow comparison of a symbol shard name with string shard name' do
|
25
|
+
u = User.using(:canada).create!(:name => 'Rafael Pilha')
|
26
|
+
expect(u).to eq(User.using('canada').find_by_name('Rafael Pilha'))
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should allow to pass a string as the shard name to a block' do
|
30
|
+
Octoball.using('canada') do
|
31
|
+
User.create!(:name => 'Rafael Pilha')
|
32
|
+
end
|
33
|
+
|
34
|
+
expect(User.using('canada').find_by_name('Rafael Pilha')).not_to be_nil
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should allow selecting the shards on scope' do
|
38
|
+
User.using(:canada).create!(:name => 'oi')
|
39
|
+
expect(User.using(:canada).count).to eq(1)
|
40
|
+
expect(User.count).to eq(0)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should allow selecting the shard using #new' do
|
44
|
+
u = User.using(:canada).new
|
45
|
+
u.name = 'Thiago'
|
46
|
+
u.save
|
47
|
+
|
48
|
+
expect(User.using(:canada).count).to eq(1)
|
49
|
+
expect(User.using(:brazil).count).to eq(0)
|
50
|
+
|
51
|
+
u1 = User.new
|
52
|
+
u1.name = 'Joaquim'
|
53
|
+
u2 = User.using(:canada).new
|
54
|
+
u2.name = 'Manuel'
|
55
|
+
u1.save
|
56
|
+
u2.save
|
57
|
+
|
58
|
+
expect(User.using(:canada).all).to eq([u, u2])
|
59
|
+
expect(User.all).to eq([u1])
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should allow the #select method to fetch the correct data when using a block" do
|
63
|
+
canadian_user = User.using(:canada).create!(:name => 'Rafael Pilha')
|
64
|
+
|
65
|
+
Octoball.using('canada') do
|
66
|
+
@all_canadian_user_ids = User.select('id').to_a
|
67
|
+
end
|
68
|
+
|
69
|
+
expect(@all_canadian_user_ids).to eq([canadian_user])
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should allow objects to be fetch using different blocks - GH #306" do
|
73
|
+
canadian_user = User.using(:canada).create!(:name => 'Rafael Pilha')
|
74
|
+
|
75
|
+
Octoball.using(:canada) { @users = User.where('id is not null') }
|
76
|
+
Octoball.using(:canada) { @user = @users.first }
|
77
|
+
|
78
|
+
Octoball.using(:canada) { @user2 = User.where('id is not null').first }
|
79
|
+
|
80
|
+
expect(@user).to eq(canadian_user)
|
81
|
+
expect(@user2).to eq(canadian_user)
|
82
|
+
end
|
83
|
+
|
84
|
+
describe 'multiple calls to the same scope' do
|
85
|
+
it 'works with nil response' do
|
86
|
+
scope = User.using(:canada)
|
87
|
+
expect(scope.count).to eq(0)
|
88
|
+
expect(scope.first).to be_nil
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'works with non-nil response' do
|
92
|
+
user = User.using(:canada).create!(:name => 'oi')
|
93
|
+
scope = User.using(:canada)
|
94
|
+
expect(scope.count).to eq(1)
|
95
|
+
expect(scope.first).to eq(user)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should select the correct shard' do
|
100
|
+
User.using(:canada)
|
101
|
+
User.create!(:name => 'oi')
|
102
|
+
expect(User.count).to eq(1)
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should ensure that the connection will be cleaned' do
|
106
|
+
expect(ActiveRecord::Base.connection.current_shard).to eq(:default)
|
107
|
+
expect do
|
108
|
+
Octoball.using(:canada) do
|
109
|
+
fail 'Some Exception'
|
110
|
+
end
|
111
|
+
end.to raise_error(RuntimeError)
|
112
|
+
|
113
|
+
expect(ActiveRecord::Base.connection.current_shard).to eq(:default)
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should allow creating more than one user' do
|
117
|
+
User.using(:canada).create([{ :name => 'America User 1' }, { :name => 'America User 2' }])
|
118
|
+
User.create!(:name => 'Thiago')
|
119
|
+
expect(User.using(:canada).find_by_name('America User 1')).not_to be_nil
|
120
|
+
expect(User.using(:canada).find_by_name('America User 2')).not_to be_nil
|
121
|
+
expect(User.using(:master).find_by_name('Thiago')).not_to be_nil
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'should clean #current_shard from proxy when using execute' do
|
125
|
+
User.using(:canada).connection.execute('select * from users limit 1;')
|
126
|
+
expect(User.connection.current_shard).to eq(:default)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should allow scoping dynamically' do
|
130
|
+
User.using(:canada).using(:master).using(:canada).create!(:name => 'oi')
|
131
|
+
expect(User.using(:canada).using(:master).count).to eq(0)
|
132
|
+
expect(User.using(:master).using(:canada).count).to eq(1)
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'should allow find inside blocks' do
|
136
|
+
@user = User.using(:brazil).create!(:name => 'Thiago')
|
137
|
+
|
138
|
+
Octoball.using(:brazil) do
|
139
|
+
expect(User.first).to eq(@user)
|
140
|
+
end
|
141
|
+
|
142
|
+
expect(User.using(:brazil).find_by_name('Thiago')).to eq(@user)
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'should clean the current_shard after executing the current query' do
|
146
|
+
User.using(:canada).create!(:name => 'oi')
|
147
|
+
expect(User.count).to eq(0)
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'should support both groups and alone shards' do
|
151
|
+
_u = User.using(:alone_shard).create!(:name => 'Alone')
|
152
|
+
expect(User.using(:alone_shard).count).to eq(1)
|
153
|
+
expect(User.using(:canada).count).to eq(0)
|
154
|
+
expect(User.using(:brazil).count).to eq(0)
|
155
|
+
expect(User.count).to eq(0)
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'should work with named scopes' do
|
159
|
+
u = User.using(:brazil).create!(:name => 'Thiago')
|
160
|
+
|
161
|
+
expect(User.thiago.using(:brazil).first).to eq(u)
|
162
|
+
expect(User.using(:brazil).thiago.first).to eq(u)
|
163
|
+
|
164
|
+
Octoball.using(:brazil) do
|
165
|
+
expect(User.thiago.first).to eq(u)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe '#current_shard attribute' do
|
170
|
+
it 'should store the attribute when you create or find an object' do
|
171
|
+
u = User.using(:alone_shard).create!(:name => 'Alone')
|
172
|
+
expect(u.current_shard).to eq(:alone_shard)
|
173
|
+
User.using(:canada).create!(:name => 'oi')
|
174
|
+
u = User.using(:canada).find_by_name('oi')
|
175
|
+
expect(u.current_shard).to eq(:canada)
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'should store the attribute when you find multiple instances' do
|
179
|
+
5.times { User.using(:alone_shard).create!(:name => 'Alone') }
|
180
|
+
|
181
|
+
User.using(:alone_shard).all.each do |u|
|
182
|
+
expect(u.current_shard).to eq(:alone_shard)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'should works when you find, and after that, alter that object' do
|
187
|
+
alone_user = User.using(:alone_shard).create!(:name => 'Alone')
|
188
|
+
_mstr_user = User.using(:master).create!(:name => 'Master')
|
189
|
+
alone_user.name = 'teste'
|
190
|
+
alone_user.save
|
191
|
+
expect(User.using(:master).first.name).to eq('Master')
|
192
|
+
expect(User.using(:alone_shard).first.name).to eq('teste')
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'should work for the reload method' do
|
196
|
+
User.using(:alone_shard).create!(:name => 'Alone')
|
197
|
+
u = User.using(:alone_shard).find_by_name('Alone')
|
198
|
+
u.reload
|
199
|
+
expect(u.name).to eq('Alone')
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'should work passing some arguments to reload method' do
|
203
|
+
User.using(:alone_shard).create!(:name => 'Alone')
|
204
|
+
u = User.using(:alone_shard).find_by_name('Alone')
|
205
|
+
u.reload(:lock => true)
|
206
|
+
expect(u.name).to eq('Alone')
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
describe 'passing a block' do
|
211
|
+
it 'should allow queries be executed inside the block, ponting to a specific shard' do
|
212
|
+
Octoball.using(:canada) do
|
213
|
+
User.create(:name => 'oi')
|
214
|
+
end
|
215
|
+
|
216
|
+
expect(User.using(:canada).count).to eq(1)
|
217
|
+
expect(User.using(:master).count).to eq(0)
|
218
|
+
expect(User.count).to eq(0)
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'should allow execute queries inside a model' do
|
222
|
+
u = User.new
|
223
|
+
u.awesome_queries
|
224
|
+
expect(User.using(:canada).count).to eq(1)
|
225
|
+
expect(User.count).to eq(0)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe 'raising errors' do
|
230
|
+
it "should raise a error when you specify a shard that doesn't exist" do
|
231
|
+
expect { User.using(:crazy_shard).create!(:name => 'Thiago') }.to raise_error(ActiveRecord::ConnectionNotEstablished)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
describe 'equality' do
|
236
|
+
let(:canada1) do
|
237
|
+
User.using(:canada).new(id: 1)
|
238
|
+
end
|
239
|
+
|
240
|
+
let(:canada1_dup) do
|
241
|
+
User.using(:canada).new(id: 1)
|
242
|
+
end
|
243
|
+
|
244
|
+
let(:brazil1) do
|
245
|
+
User.using(:brazil).new(id: 1)
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'should work with persisted objects' do
|
249
|
+
u = User.using(:brazil).create(:name => 'Mike')
|
250
|
+
expect(User.using(:brazil).find_by_name('Mike')).to eq(u)
|
251
|
+
end
|
252
|
+
|
253
|
+
it 'should check current_shard when determining equality' do
|
254
|
+
expect(canada1).not_to eq(brazil1)
|
255
|
+
expect(canada1).to eq(canada1_dup)
|
256
|
+
end
|
257
|
+
|
258
|
+
it 'delegates equality check on scopes' do
|
259
|
+
u = User.using(:brazil).create!(:name => 'Mike')
|
260
|
+
expect(User.using(:brazil).where(:name => 'Mike')).to eq([u])
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
describe 'AR basic methods' do
|
266
|
+
it 'connects_to' do
|
267
|
+
expect(CustomConnection.connection.current_database).to eq('octoball_shard_2')
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'reuses parent model connection' do
|
271
|
+
klass = Class.new(CustomConnection)
|
272
|
+
|
273
|
+
expect(klass.connection).to be klass.connection
|
274
|
+
end
|
275
|
+
|
276
|
+
it 'should not mess with custom connection table names' do
|
277
|
+
expect(Advert.connection.current_database).to eq('octoball_shard_1')
|
278
|
+
Advert.create!(:name => 'Teste')
|
279
|
+
end
|
280
|
+
|
281
|
+
it 'increment' do
|
282
|
+
_ = User.using(:brazil).create!(:name => 'Teste', :number => 10)
|
283
|
+
u = User.using(:brazil).find_by_number(10)
|
284
|
+
u.increment(:number)
|
285
|
+
u.save
|
286
|
+
expect(User.using(:brazil).find_by_number(11)).not_to be_nil
|
287
|
+
end
|
288
|
+
|
289
|
+
it 'increment!' do
|
290
|
+
_ = User.using(:brazil).create!(:name => 'Teste', :number => 10)
|
291
|
+
u = User.using(:brazil).find_by_number(10)
|
292
|
+
u.increment!(:number)
|
293
|
+
expect(User.using(:brazil).find_by_number(11)).not_to be_nil
|
294
|
+
end
|
295
|
+
|
296
|
+
it 'decrement' do
|
297
|
+
_ = User.using(:brazil).create!(:name => 'Teste', :number => 10)
|
298
|
+
u = User.using(:brazil).find_by_number(10)
|
299
|
+
u.decrement(:number)
|
300
|
+
u.save
|
301
|
+
expect(User.using(:brazil).find_by_number(9)).not_to be_nil
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'decrement!' do
|
305
|
+
_ = User.using(:brazil).create!(:name => 'Teste', :number => 10)
|
306
|
+
u = User.using(:brazil).find_by_number(10)
|
307
|
+
u.decrement!(:number)
|
308
|
+
expect(User.using(:brazil).find_by_number(9)).not_to be_nil
|
309
|
+
end
|
310
|
+
|
311
|
+
it 'toggle' do
|
312
|
+
_ = User.using(:brazil).create!(:name => 'Teste', :admin => false)
|
313
|
+
u = User.using(:brazil).find_by_name('Teste')
|
314
|
+
u.toggle(:admin)
|
315
|
+
u.save
|
316
|
+
expect(User.using(:brazil).find_by_name('Teste').admin).to be true
|
317
|
+
end
|
318
|
+
|
319
|
+
it 'toggle!' do
|
320
|
+
_ = User.using(:brazil).create!(:name => 'Teste', :admin => false)
|
321
|
+
u = User.using(:brazil).find_by_name('Teste')
|
322
|
+
u.toggle!(:admin)
|
323
|
+
expect(User.using(:brazil).find_by_name('Teste').admin).to be true
|
324
|
+
end
|
325
|
+
|
326
|
+
it 'count' do
|
327
|
+
_u = User.using(:brazil).create!(:name => 'User1')
|
328
|
+
_v = User.using(:brazil).create!(:name => 'User2')
|
329
|
+
_w = User.using(:brazil).create!(:name => 'User3')
|
330
|
+
expect(User.using(:brazil).where(:name => 'User2').all.count).to eq(1)
|
331
|
+
end
|
332
|
+
|
333
|
+
it 'maximum' do
|
334
|
+
_u = User.using(:brazil).create!(:name => 'Teste', :number => 11)
|
335
|
+
_v = User.using(:master).create!(:name => 'Teste', :number => 12)
|
336
|
+
|
337
|
+
expect(User.using(:brazil).maximum(:number)).to eq(11)
|
338
|
+
expect(User.using(:master).maximum(:number)).to eq(12)
|
339
|
+
end
|
340
|
+
|
341
|
+
it 'sum' do
|
342
|
+
u = User.using(:brazil).create!(:name => 'Teste', :number => 11)
|
343
|
+
v = User.using(:master).create!(:name => 'Teste', :number => 12)
|
344
|
+
|
345
|
+
expect(User.using(:master).sum(:number)).to eq(12)
|
346
|
+
expect(User.using(:brazil).sum(:number)).to eq(11)
|
347
|
+
|
348
|
+
expect(User.where(id: v.id).sum(:number)).to eq(12)
|
349
|
+
expect(User.using(:brazil).where(id: u.id).sum(:number)).to eq(11)
|
350
|
+
expect(User.using(:master).where(id: v.id).sum(:number)).to eq(12)
|
351
|
+
end
|
352
|
+
|
353
|
+
describe 'any?' do
|
354
|
+
before { User.using(:brazil).create!(:name => 'User1') }
|
355
|
+
|
356
|
+
it 'works when true' do
|
357
|
+
scope = User.using(:brazil).where(:name => 'User1')
|
358
|
+
expect(scope.any?).to be true
|
359
|
+
end
|
360
|
+
|
361
|
+
it 'works when false' do
|
362
|
+
scope = User.using(:brazil).where(:name => 'User2')
|
363
|
+
expect(scope.any?).to be false
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
it 'exists?' do
|
368
|
+
@user = User.using(:brazil).create!(:name => 'User1')
|
369
|
+
|
370
|
+
expect(User.using(:brazil).where(:name => 'User1').exists?).to be true
|
371
|
+
expect(User.using(:brazil).where(:name => 'User2').exists?).to be false
|
372
|
+
end
|
373
|
+
|
374
|
+
describe 'touch' do
|
375
|
+
it 'updates updated_at by default' do
|
376
|
+
@user = User.using(:brazil).create!(:name => 'User1')
|
377
|
+
User.using(:brazil).where(:id => @user.id).update_all(:updated_at => Time.now - 3.months)
|
378
|
+
@user.touch
|
379
|
+
expect(@user.reload.updated_at.in_time_zone('GMT').to_date).to eq(Time.now.in_time_zone('GMT').to_date)
|
380
|
+
end
|
381
|
+
|
382
|
+
it 'updates passed in attribute name' do
|
383
|
+
@user = User.using(:brazil).create!(:name => 'User1')
|
384
|
+
User.using(:brazil).where(:id => @user.id).update_all(:created_at => Time.now - 3.months)
|
385
|
+
@user.touch(:created_at)
|
386
|
+
expect(@user.reload.created_at.in_time_zone('GMT').to_date).to eq(Time.now.in_time_zone('GMT').to_date)
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
describe '#pluck' do
|
391
|
+
before { User.using(:brazil).create!(:name => 'User1') }
|
392
|
+
|
393
|
+
it 'should works from scope proxy' do
|
394
|
+
names = User.using(:brazil).pluck(:name)
|
395
|
+
expect(names).to eq(['User1'])
|
396
|
+
expect(User.using(:master).pluck(:name)).to eq([])
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
it 'update_column' do
|
401
|
+
@user = User.using(:brazil).create!(:name => 'User1')
|
402
|
+
@user2 = User.using(:brazil).find(@user.id)
|
403
|
+
@user2.update_column(:name, 'Joaquim Shard Brazil')
|
404
|
+
expect(User.using(:brazil).find_by_name('Joaquim Shard Brazil')).not_to be_nil
|
405
|
+
end
|
406
|
+
|
407
|
+
it 'update' do
|
408
|
+
@user = User.using(:brazil).create!(:name => 'User1')
|
409
|
+
@user2 = User.using(:brazil).find(@user.id)
|
410
|
+
@user2.update(:name => 'Joaquim')
|
411
|
+
expect(User.using(:brazil).find_by_name('Joaquim')).not_to be_nil
|
412
|
+
end
|
413
|
+
|
414
|
+
it 'using update inside a block' do
|
415
|
+
Octoball.using(:brazil) do
|
416
|
+
@user = User.create!(:name => 'User1')
|
417
|
+
@user2 = User.find(@user.id)
|
418
|
+
@user2.update(:name => 'Joaquim')
|
419
|
+
end
|
420
|
+
|
421
|
+
expect(User.find_by_name('Joaquim')).to be_nil
|
422
|
+
expect(User.using(:brazil).find_by_name('Joaquim')).not_to be_nil
|
423
|
+
end
|
424
|
+
|
425
|
+
it 'update' do
|
426
|
+
@user = User.using(:brazil).create!(:name => 'User1')
|
427
|
+
@user2 = User.using(:brazil).find(@user.id)
|
428
|
+
@user2.update(:name => 'Joaquim')
|
429
|
+
expect(User.using(:brazil).find_by_name('Joaquim')).not_to be_nil
|
430
|
+
end
|
431
|
+
|
432
|
+
it 'as_json' do
|
433
|
+
ActiveRecord::Base.include_root_in_json = false
|
434
|
+
|
435
|
+
Octoball.using(:brazil) do
|
436
|
+
User.create!(:name => 'User1')
|
437
|
+
end
|
438
|
+
|
439
|
+
user = User.using(:brazil).where(:name => 'User1').first
|
440
|
+
expect(user.as_json(:except => [:created_at, :updated_at, :id])).to eq('admin' => nil, 'name' => 'User1', 'number' => nil)
|
441
|
+
end
|
442
|
+
|
443
|
+
describe 'transaction' do
|
444
|
+
context 'without assigning a database' do
|
445
|
+
it 'works as expected' do
|
446
|
+
_u = User.create!(:name => 'Thiago')
|
447
|
+
|
448
|
+
expect(User.using(:brazil).count).to eq(0)
|
449
|
+
expect(User.using(:master).count).to eq(1)
|
450
|
+
|
451
|
+
User.using(:brazil).transaction do
|
452
|
+
expect(User.find_by_name('Thiago')).to be_nil
|
453
|
+
User.create!(:name => 'Brazil')
|
454
|
+
end
|
455
|
+
|
456
|
+
expect(User.using(:brazil).count).to eq(1)
|
457
|
+
expect(User.using(:master).count).to eq(1)
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
context 'when assigning a database' do
|
462
|
+
it 'works as expected' do
|
463
|
+
klass = User.using(:brazil)
|
464
|
+
|
465
|
+
klass.transaction do
|
466
|
+
klass.create!(:name => 'Brazil')
|
467
|
+
end
|
468
|
+
|
469
|
+
expect(klass.find_by_name('Brazil')).to be_present
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
describe "#finder methods" do
|
475
|
+
before(:each) do
|
476
|
+
@user1 = User.using(:brazil).create!(:name => 'User1')
|
477
|
+
@user2 = User.using(:brazil).create!(:name => 'User2')
|
478
|
+
@user3 = User.using(:brazil).create!(:name => 'User3')
|
479
|
+
end
|
480
|
+
|
481
|
+
it "#find_each should work with a block" do
|
482
|
+
result_array = []
|
483
|
+
|
484
|
+
User.using(:brazil).where("name is not NULL").find_each do |user|
|
485
|
+
result_array << user
|
486
|
+
end
|
487
|
+
|
488
|
+
expect(result_array).to eq([@user1, @user2, @user3])
|
489
|
+
end
|
490
|
+
|
491
|
+
it "#find_each should work with a where.not(...)" do
|
492
|
+
result_array = []
|
493
|
+
|
494
|
+
User.using(:brazil).where.not(:name => 'User2').find_each do |user|
|
495
|
+
result_array << user
|
496
|
+
end
|
497
|
+
|
498
|
+
expect(result_array).to eq([@user1, @user3])
|
499
|
+
end
|
500
|
+
|
501
|
+
it "#find_each should work as an enumerator" do
|
502
|
+
result_array = []
|
503
|
+
|
504
|
+
User.using(:brazil).where("name is not NULL").find_each.each do |user|
|
505
|
+
result_array << user
|
506
|
+
end
|
507
|
+
|
508
|
+
expect(result_array).to eq([@user1, @user2, @user3])
|
509
|
+
end
|
510
|
+
|
511
|
+
it "#find_each should work as a lazy enumerator" do
|
512
|
+
result_array = []
|
513
|
+
|
514
|
+
User.using(:brazil).where("name is not NULL").find_each.lazy.each do |user|
|
515
|
+
result_array << user
|
516
|
+
end
|
517
|
+
|
518
|
+
expect(result_array).to eq([@user1, @user2, @user3])
|
519
|
+
end
|
520
|
+
|
521
|
+
it "#find_in_batches should work with a block" do
|
522
|
+
result_array = []
|
523
|
+
|
524
|
+
User.using(:brazil).where("name is not NULL").find_in_batches(batch_size: 1) do |user|
|
525
|
+
result_array << user
|
526
|
+
end
|
527
|
+
|
528
|
+
expect(result_array).to eq([[@user1], [@user2], [@user3]])
|
529
|
+
end
|
530
|
+
|
531
|
+
it "#find_in_batches should work as an enumerator" do
|
532
|
+
result_array = []
|
533
|
+
|
534
|
+
User.using(:brazil).where("name is not NULL").find_in_batches(batch_size: 1).each do |user|
|
535
|
+
result_array << user
|
536
|
+
end
|
537
|
+
|
538
|
+
expect(result_array).to eq([[@user1], [@user2], [@user3]])
|
539
|
+
end
|
540
|
+
|
541
|
+
it "#find_in_batches should work as a lazy enumerator" do
|
542
|
+
result_array = []
|
543
|
+
|
544
|
+
User.using(:brazil).where("name is not NULL").find_in_batches(batch_size: 1).lazy.each do |user|
|
545
|
+
result_array << user
|
546
|
+
end
|
547
|
+
|
548
|
+
expect(result_array).to eq([[@user1], [@user2], [@user3]])
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
describe 'deleting a record' do
|
553
|
+
before(:each) do
|
554
|
+
@user = User.using(:brazil).create!(:name => 'User1')
|
555
|
+
@user2 = User.using(:brazil).find(@user.id)
|
556
|
+
end
|
557
|
+
|
558
|
+
it 'delete' do
|
559
|
+
@user2.delete
|
560
|
+
expect { User.using(:brazil).find(@user2.id) }.to raise_error(ActiveRecord::RecordNotFound)
|
561
|
+
end
|
562
|
+
|
563
|
+
it "delete within block shouldn't lose shard" do
|
564
|
+
Octoball.using(:brazil) do
|
565
|
+
@user2.delete
|
566
|
+
@user3 = User.create(:name => 'User3')
|
567
|
+
|
568
|
+
expect(User.connection.current_shard).to eq(:brazil)
|
569
|
+
expect(User.find(@user3.id)).to eq(@user3)
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
it 'destroy' do
|
574
|
+
@user2.destroy
|
575
|
+
expect { User.using(:brazil).find(@user2.id) }.to raise_error(ActiveRecord::RecordNotFound)
|
576
|
+
end
|
577
|
+
|
578
|
+
it "destroy within block shouldn't lose shard" do
|
579
|
+
Octoball.using(:brazil) do
|
580
|
+
@user2.destroy
|
581
|
+
@user3 = User.create(:name => 'User3')
|
582
|
+
|
583
|
+
expect(User.connection.current_shard).to eq(:brazil)
|
584
|
+
expect(User.find(@user3.id)).to eq(@user3)
|
585
|
+
end
|
586
|
+
end
|
587
|
+
end
|
588
|
+
end
|
589
|
+
|
590
|
+
describe 'custom connection' do
|
591
|
+
context 'by default' do
|
592
|
+
it 'with plain call should use custom connection' do
|
593
|
+
expect(CustomConnection.connection.current_database).to eq('octoball_shard_2')
|
594
|
+
end
|
595
|
+
|
596
|
+
it 'should use model-specific shard' do
|
597
|
+
expect(CustomConnection.using(:custom_shard).connection.current_database).to eq('octoball_shard_3')
|
598
|
+
end
|
599
|
+
|
600
|
+
it 'should use model-specific shard by Octoball.using block' do
|
601
|
+
Octoball.using(:custom_shard) do
|
602
|
+
expect(CustomConnection.connection.current_database).to eq('octoball_shard_3')
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
it 'should save to correct shard' do
|
607
|
+
expect { CustomConnection.create(:value => 'custom value') }.to change {
|
608
|
+
CustomConnection
|
609
|
+
.connection
|
610
|
+
.execute("select count(*) as ct from custom where value = 'custom value'")
|
611
|
+
.to_a.first.first
|
612
|
+
}.by 1
|
613
|
+
end
|
614
|
+
end
|
615
|
+
|
616
|
+
describe 'clear_active_connections!' do
|
617
|
+
it 'should not leak connection' do
|
618
|
+
CustomConnection.create(:value => 'custom value')
|
619
|
+
|
620
|
+
# This is what Rails, Sidekiq etc call--this normally handles all connection pools in the app
|
621
|
+
expect { ActiveRecord::Base.clear_active_connections! }
|
622
|
+
.to change { CustomConnection.connection_pool.active_connection? }
|
623
|
+
|
624
|
+
expect(CustomConnection.connection_pool.active_connection?).to be_falsey
|
625
|
+
end
|
626
|
+
end
|
627
|
+
end
|
628
|
+
|
629
|
+
describe 'when using set_table_name' do
|
630
|
+
it 'should work correctly' do
|
631
|
+
Bacon.using(:brazil).create!(:name => 'YUMMMYYYY')
|
632
|
+
end
|
633
|
+
|
634
|
+
it 'should work correctly with a block' do
|
635
|
+
Cheese.using(:brazil).create!(:name => 'YUMMMYYYY')
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
describe 'when using table_name=' do
|
640
|
+
it 'should work correctly' do
|
641
|
+
Ham.using(:brazil).create!(:name => 'YUMMMYYYY')
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
describe 'when you have joins/include' do
|
646
|
+
before(:each) do
|
647
|
+
@client1 = Client.using(:brazil).create(:name => 'Thiago')
|
648
|
+
|
649
|
+
Octoball.using(:canada) do
|
650
|
+
@client2 = Client.create(:name => 'Mike')
|
651
|
+
@client3 = Client.create(:name => 'Joao')
|
652
|
+
@item1 = Item.create(:client => @client2, :name => 'Item 1')
|
653
|
+
@item2 = Item.create(:client => @client2, :name => 'Item 2')
|
654
|
+
@item3 = Item.create(:client => @client3, :name => 'Item 3')
|
655
|
+
@part1 = Part.create(:item => @item1, :name => 'Part 1')
|
656
|
+
@part2 = Part.create(:item => @item1, :name => 'Part 2')
|
657
|
+
@part3 = Part.create(:item => @item2, :name => 'Part 3')
|
658
|
+
end
|
659
|
+
|
660
|
+
@item4 = Item.using(:brazil).create(:client => @client1, :name => 'Item 4')
|
661
|
+
end
|
662
|
+
|
663
|
+
it 'should work using the rails 3.x syntax' do
|
664
|
+
items = Item.using(:canada).joins(:client).where("clients.id = #{@client2.id}").all
|
665
|
+
expect(items).to eq([@item1, @item2])
|
666
|
+
end
|
667
|
+
|
668
|
+
it 'should work for include also, rails 3.x syntax' do
|
669
|
+
items = Item.using(:canada).includes(:client).where(:clients => { :id => @client2.id }).all
|
670
|
+
expect(items).to eq([@item1, @item2])
|
671
|
+
end
|
672
|
+
end
|
673
|
+
|
674
|
+
describe 'ActiveRecord::Base Validations' do
|
675
|
+
it 'should work correctly when using validations' do
|
676
|
+
@key = Keyboard.create!(:name => 'Key')
|
677
|
+
expect { Keyboard.using(:brazil).create!(:name => 'Key') }.not_to raise_error
|
678
|
+
expect { Keyboard.create!(:name => 'Key') }.to raise_error(ActiveRecord::RecordInvalid)
|
679
|
+
end
|
680
|
+
|
681
|
+
it 'should work correctly when using validations with using syntax' do
|
682
|
+
@key = Keyboard.using(:brazil).create!(:name => 'Key')
|
683
|
+
expect { Keyboard.create!(:name => 'Key') }.not_to raise_error
|
684
|
+
expect { Keyboard.using(:brazil).create!(:name => 'Key') }
|
685
|
+
.to raise_error(ActiveRecord::RecordInvalid)
|
686
|
+
end
|
687
|
+
end
|
688
|
+
end
|