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