replidog 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/Appraisals +17 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +188 -0
  7. data/Rakefile +2 -0
  8. data/gemfiles/rails32.gemfile +7 -0
  9. data/gemfiles/rails4.gemfile +7 -0
  10. data/gemfiles/rails41.gemfile +7 -0
  11. data/gemfiles/rails42.gemfile +7 -0
  12. data/lib/replidog/active_record.rb +1 -0
  13. data/lib/replidog/model.rb +89 -0
  14. data/lib/replidog/proxy.rb +189 -0
  15. data/lib/replidog/proxy_handler.rb +68 -0
  16. data/lib/replidog/scope_proxy.rb +24 -0
  17. data/lib/replidog/version.rb +3 -0
  18. data/lib/replidog.rb +7 -0
  19. data/replidog.gemspec +43 -0
  20. data/spec/dummy/Rakefile +7 -0
  21. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  22. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  23. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  24. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  25. data/spec/dummy/app/models/admin.rb +3 -0
  26. data/spec/dummy/app/models/ingredient.rb +3 -0
  27. data/spec/dummy/app/models/profile.rb +3 -0
  28. data/spec/dummy/app/models/recipe.rb +6 -0
  29. data/spec/dummy/app/models/user.rb +3 -0
  30. data/spec/dummy/app/models/user_table.rb +5 -0
  31. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  32. data/spec/dummy/config/application.rb +51 -0
  33. data/spec/dummy/config/boot.rb +10 -0
  34. data/spec/dummy/config/database.yml +65 -0
  35. data/spec/dummy/config/environment.rb +5 -0
  36. data/spec/dummy/config/environments/development.rb +23 -0
  37. data/spec/dummy/config/environments/production.rb +71 -0
  38. data/spec/dummy/config/environments/test.rb +33 -0
  39. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  40. data/spec/dummy/config/initializers/inflections.rb +15 -0
  41. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  42. data/spec/dummy/config/initializers/secret_token.rb +8 -0
  43. data/spec/dummy/config/initializers/session_store.rb +8 -0
  44. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  45. data/spec/dummy/config/locales/en.yml +5 -0
  46. data/spec/dummy/config/routes.rb +2 -0
  47. data/spec/dummy/config.ru +4 -0
  48. data/spec/dummy/db/schema.rb +46 -0
  49. data/spec/dummy/public/404.html +26 -0
  50. data/spec/dummy/public/422.html +26 -0
  51. data/spec/dummy/public/500.html +25 -0
  52. data/spec/dummy/public/favicon.ico +0 -0
  53. data/spec/dummy/script/rails +6 -0
  54. data/spec/replidog/model_spec.rb +273 -0
  55. data/spec/replidog/proxy_spec.rb +58 -0
  56. data/spec/spec_helper.rb +52 -0
  57. 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
@@ -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