replidog 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/.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
|