replidog 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/Appraisals +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +188 -0
- data/Rakefile +2 -0
- data/gemfiles/rails32.gemfile +7 -0
- data/gemfiles/rails4.gemfile +7 -0
- data/gemfiles/rails41.gemfile +7 -0
- data/gemfiles/rails42.gemfile +7 -0
- data/lib/replidog/active_record.rb +1 -0
- data/lib/replidog/model.rb +89 -0
- data/lib/replidog/proxy.rb +189 -0
- data/lib/replidog/proxy_handler.rb +68 -0
- data/lib/replidog/scope_proxy.rb +24 -0
- data/lib/replidog/version.rb +3 -0
- data/lib/replidog.rb +7 -0
- data/replidog.gemspec +43 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/admin.rb +3 -0
- data/spec/dummy/app/models/ingredient.rb +3 -0
- data/spec/dummy/app/models/profile.rb +3 -0
- data/spec/dummy/app/models/recipe.rb +6 -0
- data/spec/dummy/app/models/user.rb +3 -0
- data/spec/dummy/app/models/user_table.rb +5 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config/application.rb +51 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +65 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +23 -0
- data/spec/dummy/config/environments/production.rb +71 -0
- data/spec/dummy/config/environments/test.rb +33 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +8 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +2 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/schema.rb +46 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/replidog/model_spec.rb +273 -0
- data/spec/replidog/proxy_spec.rb +58 -0
- data/spec/spec_helper.rb +52 -0
- metadata +388 -0
@@ -0,0 +1,273 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Replidog::Model do
|
4
|
+
def activate_connection_pools
|
5
|
+
Recipe.using(:master).first
|
6
|
+
Recipe.using(:slave1).first
|
7
|
+
Recipe.using(:slave2).first
|
8
|
+
Recipe.using(:slave3).first
|
9
|
+
User.using(:master).first
|
10
|
+
User.using(:slave).first
|
11
|
+
end
|
12
|
+
|
13
|
+
describe ".extended" do
|
14
|
+
it "shares the same proxy_handler" do
|
15
|
+
expect(Recipe.proxy_handler).to eq User.proxy_handler
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe ".establish_connection" do
|
20
|
+
context "with no args" do
|
21
|
+
it 'establishes connection' do
|
22
|
+
expect(Recipe.connection.current_database).to eq "dummy_test"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "with args" do
|
27
|
+
it 'establishes connection' do
|
28
|
+
expect(User.connection.current_database).to eq "dummy_test_user"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe ".replicated?" do
|
34
|
+
context "with replicated model" do
|
35
|
+
it "returns true" do
|
36
|
+
expect(Recipe).to be_replicated
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "with not replicated model" do
|
41
|
+
it "returns false" do
|
42
|
+
expect(Admin).not_to be_replicated
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe ".connection" do
|
48
|
+
context "with replicated model" do
|
49
|
+
it "returns proxy object" do
|
50
|
+
expect(Recipe.connection).to be_a Replidog::Proxy
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "with not replicated model" do
|
55
|
+
it "returns proxy object" do
|
56
|
+
expect(Admin.connection).not_to be_a Replidog::Proxy
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "with inherited model" do
|
61
|
+
it "returns the same proxy object" do
|
62
|
+
expect(Recipe.connection).to eq Ingredient.connection
|
63
|
+
end
|
64
|
+
|
65
|
+
it "returns the same proxy object" do
|
66
|
+
expect(User.connection).to eq Profile.connection
|
67
|
+
end
|
68
|
+
|
69
|
+
it "returns other proxy object" do
|
70
|
+
expect(Recipe.connection).not_to eq User.connection
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "with proxy" do
|
75
|
+
it "proxies INSERT to master & SELECT to replications" do
|
76
|
+
Recipe.create(title: "test")
|
77
|
+
expect(Recipe.first).to be_nil
|
78
|
+
expect(Recipe.first).to be_nil
|
79
|
+
expect(Recipe.first).to be_nil
|
80
|
+
end
|
81
|
+
|
82
|
+
it "selects replications by roundrobin order" do
|
83
|
+
Recipe.using(:slave1).create(title: "test")
|
84
|
+
Recipe.connection.index = 0
|
85
|
+
expect(Recipe.first).not_to be_nil
|
86
|
+
expect(Recipe.first).to be_nil
|
87
|
+
expect(Recipe.first).to be_nil
|
88
|
+
expect(Recipe.first).not_to be_nil
|
89
|
+
expect(Recipe.first).to be_nil
|
90
|
+
expect(Recipe.first).to be_nil
|
91
|
+
expect(Recipe.first).not_to be_nil
|
92
|
+
expect(Recipe.first).to be_nil
|
93
|
+
expect(Recipe.first).to be_nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe ".using" do
|
99
|
+
context "with :master" do
|
100
|
+
it "executes SQL query on master connection" do
|
101
|
+
Recipe.create(title: "test")
|
102
|
+
expect(Recipe.using(:master).first).not_to be_nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "with slave name" do
|
107
|
+
after do
|
108
|
+
Recipe.using(:slave1).destroy_all
|
109
|
+
end
|
110
|
+
|
111
|
+
it "executes SQL query on specified slave" do
|
112
|
+
Recipe.using(:slave1).create(title: "test")
|
113
|
+
expect(Recipe.using(:slave1).first).not_to be_nil
|
114
|
+
expect(Recipe.using(:slave2).first).to be_nil
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context "with block" do
|
119
|
+
it "forces the receiver to use specified connection in the passed block" do
|
120
|
+
Recipe.using(:slave1).create(title: "test")
|
121
|
+
Recipe.using(:slave1) {
|
122
|
+
expect(Recipe.first).not_to be_nil
|
123
|
+
}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "with no block" do
|
128
|
+
it "can be used as scope" do
|
129
|
+
Recipe.using(:slave1).create(title: "test")
|
130
|
+
expect(Recipe.using(:slave1).first).not_to be_nil
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context "with scope" do
|
135
|
+
it "works well" do
|
136
|
+
Recipe.recent.using(:slave1).create(title: "test")
|
137
|
+
expect(Recipe.where(title: "test").using(:slave1).first).not_to be_nil
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context "with belongs_to association" do
|
142
|
+
let!(:ingredient) do
|
143
|
+
Ingredient.using(:slave1).create(name: "test", recipe_id: recipe.id)
|
144
|
+
end
|
145
|
+
|
146
|
+
let(:recipe) do
|
147
|
+
Recipe.using(:slave1).create(title: "test")
|
148
|
+
end
|
149
|
+
|
150
|
+
it "works well" do
|
151
|
+
Recipe.using(:slave1) do
|
152
|
+
expect(ingredient.recipe).to eq recipe
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context "with has_many association" do
|
158
|
+
let!(:ingredient) do
|
159
|
+
Ingredient.using(:slave1).create(name: "test", recipe_id: recipe.id)
|
160
|
+
end
|
161
|
+
|
162
|
+
let(:recipe) do
|
163
|
+
Recipe.using(:slave1).create(title: "test")
|
164
|
+
end
|
165
|
+
|
166
|
+
it "works well" do
|
167
|
+
Ingredient.using(:slave1) do
|
168
|
+
expect(recipe.ingredients).to eq [ingredient]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "with has_one association" do
|
174
|
+
let!(:profile) do
|
175
|
+
Profile.using(:slave).create(nickname: "test", user_id: user.id)
|
176
|
+
end
|
177
|
+
|
178
|
+
let(:user) do
|
179
|
+
User.using(:slave).create(name: "test")
|
180
|
+
end
|
181
|
+
|
182
|
+
it "works well" do
|
183
|
+
Profile.using(:slave) do
|
184
|
+
expect(user.profile).to eq profile
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe "#transaction" do
|
191
|
+
context "without using" do
|
192
|
+
it "executes SQL query on master connection" do
|
193
|
+
Recipe.using(:slave1).create(title: "test")
|
194
|
+
Recipe.using(:slave2).create(title: "test")
|
195
|
+
Recipe.using(:slave3).create(title: "test")
|
196
|
+
Recipe.transaction do
|
197
|
+
expect(Recipe.first).to be_nil
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
context "with using slave name" do
|
203
|
+
it "executes SQL query on slave connection" do
|
204
|
+
Recipe.using(:slave1).create(title: "test")
|
205
|
+
Recipe.using(:slave1).transaction do
|
206
|
+
expect(Recipe.first).not_to be_nil
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
describe ".clear_active_connections!" do
|
213
|
+
before do
|
214
|
+
activate_connection_pools
|
215
|
+
end
|
216
|
+
|
217
|
+
it "deactivates all connections" do
|
218
|
+
ActiveRecord::Base.clear_active_connections!
|
219
|
+
|
220
|
+
expect(Recipe.connection_pool).not_to be_active_connection
|
221
|
+
Recipe.connection.send(:slave_connection_pool_table).each_value do |connection_pool|
|
222
|
+
expect(connection_pool).not_to be_active_connection
|
223
|
+
end
|
224
|
+
|
225
|
+
expect(User.connection_pool).not_to be_active_connection
|
226
|
+
User.connection.send(:slave_connection_pool_table).each_value do |connection_pool|
|
227
|
+
expect(connection_pool).not_to be_active_connection
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
describe ".clear_reloadable_connections!" do
|
233
|
+
before do
|
234
|
+
activate_connection_pools
|
235
|
+
end
|
236
|
+
|
237
|
+
context "with mysql2 adapter" do
|
238
|
+
it "deactivates all connections" do
|
239
|
+
ActiveRecord::Base.clear_reloadable_connections!
|
240
|
+
|
241
|
+
expect(Recipe.connection_pool).not_to be_active_connection
|
242
|
+
Recipe.connection.send(:slave_connection_pool_table).each_value do |connection_pool|
|
243
|
+
expect(connection_pool).not_to be_active_connection
|
244
|
+
end
|
245
|
+
|
246
|
+
expect(User.connection_pool).not_to be_active_connection
|
247
|
+
User.connection.send(:slave_connection_pool_table).each_value do |connection_pool|
|
248
|
+
expect(connection_pool).not_to be_active_connection
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
describe ".clear_all_connections!" do
|
255
|
+
before do
|
256
|
+
activate_connection_pools
|
257
|
+
end
|
258
|
+
|
259
|
+
it "clears all connections" do
|
260
|
+
ActiveRecord::Base.clear_all_connections!
|
261
|
+
|
262
|
+
expect(Recipe).not_to be_connected
|
263
|
+
Recipe.connection.send(:slave_connection_pool_table).each_value do |connection_pool|
|
264
|
+
expect(connection_pool).not_to be_connected
|
265
|
+
end
|
266
|
+
|
267
|
+
expect(User).not_to be_connected
|
268
|
+
User.connection.send(:slave_connection_pool_table).each_value do |connection_pool|
|
269
|
+
expect(connection_pool).not_to be_connected
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Replidog::Proxy do
|
4
|
+
before do
|
5
|
+
# call establish_connection
|
6
|
+
UserTable
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#enable_query_cache!" do
|
10
|
+
it "enables query cache of all connections" do
|
11
|
+
expect{ Recipe.connection.enable_query_cache! }
|
12
|
+
.to change { Recipe.using(:master).connection.query_cache_enabled }.from(false).to(true)
|
13
|
+
.and change { Recipe.using(:slave1).connection.query_cache_enabled }.from(false).to(true)
|
14
|
+
.and change { Recipe.using(:slave2).connection.query_cache_enabled }.from(false).to(true)
|
15
|
+
.and change { Recipe.using(:slave3).connection.query_cache_enabled }.from(false).to(true)
|
16
|
+
.and change { User.using(:master).connection.query_cache_enabled }.from(false).to(true)
|
17
|
+
.and change { User.using(:slave).connection.query_cache_enabled }.from(false).to(true)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#disable_query_cache!" do
|
22
|
+
before do
|
23
|
+
ActiveRecord::Base.connection.enable_query_cache!
|
24
|
+
end
|
25
|
+
|
26
|
+
it "disables query cache of all connections" do
|
27
|
+
expect{ Recipe.connection.disable_query_cache! }
|
28
|
+
.to change { Recipe.using(:master).connection.query_cache_enabled }.from(true).to(false)
|
29
|
+
.and change { Recipe.using(:slave1).connection.query_cache_enabled }.from(true).to(false)
|
30
|
+
.and change { Recipe.using(:slave2).connection.query_cache_enabled }.from(true).to(false)
|
31
|
+
.and change { Recipe.using(:slave3).connection.query_cache_enabled }.from(true).to(false)
|
32
|
+
.and change { User.using(:master).connection.query_cache_enabled }.from(true).to(false)
|
33
|
+
.and change { User.using(:slave).connection.query_cache_enabled }.from(true).to(false)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#clear_query_cache!" do
|
38
|
+
before do
|
39
|
+
ActiveRecord::Base.connection.enable_query_cache!
|
40
|
+
Recipe.using(:master).first
|
41
|
+
Recipe.using(:slave1).first
|
42
|
+
Recipe.using(:slave2).first
|
43
|
+
Recipe.using(:slave3).first
|
44
|
+
User.using(:master).first
|
45
|
+
User.using(:slave).first
|
46
|
+
end
|
47
|
+
|
48
|
+
it "clear query cache of all connections" do
|
49
|
+
expect{ Recipe.connection.clear_query_cache }
|
50
|
+
.to change { Recipe.using(:master).connection.query_cache }.to({})
|
51
|
+
.and change { Recipe.using(:slave1).connection.query_cache }.to({})
|
52
|
+
.and change { Recipe.using(:slave2).connection.query_cache }.to({})
|
53
|
+
.and change { Recipe.using(:slave3).connection.query_cache }.to({})
|
54
|
+
.and change { User.using(:master).connection.query_cache }.to({})
|
55
|
+
.and change { User.using(:slave).connection.query_cache }.to({})
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
2
|
+
require "replidog"
|
3
|
+
require "database_rewinder"
|
4
|
+
require "pry"
|
5
|
+
|
6
|
+
ENV["RAILS_ENV"] ||= "test"
|
7
|
+
require File.expand_path("../dummy/config/environment", __FILE__)
|
8
|
+
require "rspec/rails"
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
copy_master_to_slave = proc do |adapter|
|
12
|
+
case adapter
|
13
|
+
when :mysql2
|
14
|
+
3.times do |i|
|
15
|
+
system("mysql -u root -e 'drop database dummy_test_slave#{i + 1}' > /dev/null 2> /dev/null")
|
16
|
+
system("mysql -u root -e 'create database dummy_test_slave#{i + 1}'")
|
17
|
+
system("mysqldump -u root dummy_test | mysql -u root dummy_test_slave#{i + 1}")
|
18
|
+
end
|
19
|
+
system("mysql -u root -e 'drop database dummy_test_user' > /dev/null 2> /dev/null")
|
20
|
+
system("mysql -u root -e 'drop database dummy_test_user_slave' > /dev/null 2> /dev/null")
|
21
|
+
system("mysql -u root -e 'create database dummy_test_user'")
|
22
|
+
system("mysql -u root -e 'create database dummy_test_user_slave'")
|
23
|
+
system("mysqldump -u root dummy_test | mysql -u root dummy_test_user")
|
24
|
+
system("mysqldump -u root dummy_test | mysql -u root dummy_test_user_slave")
|
25
|
+
when :sqlite3
|
26
|
+
3.times do |i|
|
27
|
+
FileUtils.copy("#{Rails.root}/db/test.sqlite3", "#{Rails.root}/db/test_slave#{i + 1}.sqlite3")
|
28
|
+
end
|
29
|
+
FileUtils.copy("#{Rails.root}/db/test.sqlite3", "#{Rails.root}/db/test_user.sqlite3")
|
30
|
+
FileUtils.copy("#{Rails.root}/db/test.sqlite3", "#{Rails.root}/db/test_user_slave.sqlite3")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
config.run_all_when_everything_filtered = true
|
35
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
36
|
+
config.filter_run :focus
|
37
|
+
|
38
|
+
config.before(:suite) do
|
39
|
+
DatabaseRewinder["test"]
|
40
|
+
DatabaseRewinder["test_slave1"]
|
41
|
+
DatabaseRewinder["test_slave2"]
|
42
|
+
DatabaseRewinder["test_slave3"]
|
43
|
+
DatabaseRewinder["test_user"]
|
44
|
+
DatabaseRewinder["test_user_slave"]
|
45
|
+
DatabaseRewinder.clean_all
|
46
|
+
copy_master_to_slave.call(:mysql2)
|
47
|
+
end
|
48
|
+
|
49
|
+
config.after(:each) do
|
50
|
+
DatabaseRewinder.clean
|
51
|
+
end
|
52
|
+
end
|