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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2419525361d0fe6b11f5150aa4641aaa33358978
|
4
|
+
data.tar.gz: 954cdd81a2a5056cf153083d3315e6d5ef39e020
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 48aab863f60e64c3efc4aa8f0ca6fe64a3830e0e5b0a3efcfb8a5dd9469aae8b0a8f3c6ff9df1f4767d7afcc43db879488038625d3f51ef25f258f74d97dd1b2
|
7
|
+
data.tar.gz: ff4b99cd46fe00763a8f82caf7fcbf14ba9c33417bb3993799ddcd669f8fcb827ff61349db335243f68d355c3d5026227eba698f022d0dfb4a867963551ae7ac
|
data/.gitignore
ADDED
data/Appraisals
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
appraise "rails32" do
|
2
|
+
gem "activerecord", "~> 3.2.0"
|
3
|
+
end
|
4
|
+
|
5
|
+
appraise "rails4" do
|
6
|
+
gem "activerecord", "~> 4.0.0"
|
7
|
+
end
|
8
|
+
|
9
|
+
appraise "rails41" do
|
10
|
+
gem "activerecord", "~> 4.1.0"
|
11
|
+
end
|
12
|
+
|
13
|
+
appraise "rails42" do
|
14
|
+
gem "activerecord", "~> 4.2.0"
|
15
|
+
end
|
16
|
+
# vim: ft=ruby
|
17
|
+
#
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Manabu Ejima
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
# Replidog
|
2
|
+
|
3
|
+
Multiple mlaster/slave helper for ActiveRecord
|
4
|
+
|
5
|
+
Based on https://github.com/r7kamura/replicat
|
6
|
+
|
7
|
+
## Features
|
8
|
+
|
9
|
+
* Multiple master/slave
|
10
|
+
* Auto switching between master/slave
|
11
|
+
* Supports connection management and query cache
|
12
|
+
* Supports Rails 3.2, 4.0, 4,1, 4.2
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'replidog'
|
20
|
+
```
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
```sh
|
25
|
+
$ bundle
|
26
|
+
```
|
27
|
+
|
28
|
+
Or install it yourself as:
|
29
|
+
|
30
|
+
```sh
|
31
|
+
$ gem install replidog
|
32
|
+
```
|
33
|
+
|
34
|
+
## Usage
|
35
|
+
|
36
|
+
### configuration
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
# config/database.yml
|
40
|
+
production:
|
41
|
+
adapter: mysql2
|
42
|
+
encoding: utf8
|
43
|
+
host: 192.168.24.1
|
44
|
+
port: 3306
|
45
|
+
replications:
|
46
|
+
slave1:
|
47
|
+
adapter: mysql2
|
48
|
+
encoding: utf8
|
49
|
+
host: 192.168.24.2
|
50
|
+
port: 3306
|
51
|
+
slave2:
|
52
|
+
adapter: mysql2
|
53
|
+
encoding: utf8
|
54
|
+
host: 192.168.24.3
|
55
|
+
port: 3306
|
56
|
+
slave3:
|
57
|
+
adapter: mysql2
|
58
|
+
encoding: utf8
|
59
|
+
host: 192.168.24.4
|
60
|
+
port: 3306
|
61
|
+
```
|
62
|
+
|
63
|
+
### replication
|
64
|
+
|
65
|
+
Now SELECT queries of User model will be sent to slave connections.
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
# INSERT query is sent to master.
|
69
|
+
User.create(name: "replicat")
|
70
|
+
|
71
|
+
# SELECT query is sent to slave.
|
72
|
+
User.first
|
73
|
+
```
|
74
|
+
|
75
|
+
### using
|
76
|
+
|
77
|
+
`using` can help you specify particular connection.
|
78
|
+
When you want to send queries to master,
|
79
|
+
you can use `using(:master)` to do that (:master is reserved name for `using` method).
|
80
|
+
When you want to send queries to a particular slave,
|
81
|
+
you can use the slave's name on database.yml like `using(:slave1)`.
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
# SELECT query is sent to master.
|
85
|
+
User.using(:master).first
|
86
|
+
|
87
|
+
# INSERT query is sent to slave1.
|
88
|
+
User.using(:slave1).create(name: "replicat")
|
89
|
+
|
90
|
+
# :slave1 is used for User connection in the passed block.
|
91
|
+
User.using(:slave1) { blog.user }
|
92
|
+
```
|
93
|
+
|
94
|
+
### round-robin
|
95
|
+
|
96
|
+
slave connections are balanced by round-robin way.
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
User.first # sent to slave1
|
100
|
+
User.first # sent to slave2
|
101
|
+
User.first # sent to slave3
|
102
|
+
User.first # sent to slave1
|
103
|
+
User.first # sent to slave2
|
104
|
+
User.first # sent to slave3
|
105
|
+
User.first # sent to slave1
|
106
|
+
User.first # sent to slave2
|
107
|
+
User.first # sent to slave3
|
108
|
+
```
|
109
|
+
|
110
|
+
### multi master-slave set
|
111
|
+
|
112
|
+
Pass the master's connection name to `replicate` method.
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
# app/models/recipe.rb
|
116
|
+
class Recipe < RecipeTable
|
117
|
+
establish_connection :production_recipe
|
118
|
+
end
|
119
|
+
|
120
|
+
# config/database.yml
|
121
|
+
production_base:
|
122
|
+
adapter: mysql2
|
123
|
+
encoding: utf8
|
124
|
+
port: 3306
|
125
|
+
production:
|
126
|
+
<<: *production_base
|
127
|
+
host: 192.168.24.1
|
128
|
+
replications:
|
129
|
+
slave1:
|
130
|
+
<<: *slave
|
131
|
+
host: 192.168.24.2
|
132
|
+
slave2:
|
133
|
+
<<: *slave
|
134
|
+
host: 192.168.24.3
|
135
|
+
slave3:
|
136
|
+
<<: *slave
|
137
|
+
host: 192.168.24.4
|
138
|
+
|
139
|
+
production_recipe:
|
140
|
+
<<: *production_base
|
141
|
+
host: 192.168.24.5
|
142
|
+
replications:
|
143
|
+
slave1:
|
144
|
+
<<: *slave
|
145
|
+
host: 192.168.24.6
|
146
|
+
slave2:
|
147
|
+
<<: *slave
|
148
|
+
host: 192.168.24.7
|
149
|
+
slave3:
|
150
|
+
<<: *slave
|
151
|
+
host: 192.168.24.8
|
152
|
+
```
|
153
|
+
|
154
|
+
If you want to connecto to `production_recipe` in multiple models, Create Abstract class and extend it.
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
# app/models/recipe_table.rb
|
158
|
+
class RecipeTable < ActiveRecord::Base
|
159
|
+
self.abstract_class = true
|
160
|
+
establish_connection :production_recipe
|
161
|
+
end
|
162
|
+
|
163
|
+
# app/models/recipe.rb
|
164
|
+
class Recipe < RecipeTable
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
### connection management / query cache
|
169
|
+
|
170
|
+
To handle all master/slave connections togegher, the methods related with connection management and query cache are overridden.
|
171
|
+
So you don't need to update middlewares and configurations for app servers.
|
172
|
+
|
173
|
+
List of overridden methods
|
174
|
+
* ActiveRecord::Base.clear_active_connections
|
175
|
+
* ActiveRecord::Base.clear_reloadable_connections
|
176
|
+
* ActiveRecord::Base.clear_all_connections
|
177
|
+
* ActiveRecord::Base.connection.enable_query_cache!
|
178
|
+
* ActiveRecord::Base.connection.disable_query_cache!
|
179
|
+
* ActiveRecord::Base.connection.clear_query_cache!
|
180
|
+
|
181
|
+
|
182
|
+
## Contributing
|
183
|
+
|
184
|
+
1. Fork it ( https://github.com/[my-github-username]/replidog/fork )
|
185
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
186
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
187
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
188
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ActiveRecord::Base.extend Replidog::Model
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require "active_support/core_ext/module/aliasing"
|
2
|
+
require "active_support/core_ext/class/attribute"
|
3
|
+
|
4
|
+
module Replidog
|
5
|
+
module Model
|
6
|
+
def self.extended(base)
|
7
|
+
base.class_attribute :proxy_handler, instance_writer: false
|
8
|
+
base.proxy_handler = Replidog::ProxyHandler.new
|
9
|
+
|
10
|
+
class << base
|
11
|
+
alias_method_chain(:establish_connection, :replidog)
|
12
|
+
alias_method_chain(:connection, :replidog)
|
13
|
+
alias_method_chain(:connected?, :replidog)
|
14
|
+
alias_method_chain(:clear_reloadable_connections!, :replidog)
|
15
|
+
alias_method_chain(:clear_active_connections!, :replidog)
|
16
|
+
alias_method_chain(:clear_all_connections!, :replidog)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def establish_connection_with_replidog(spec = ENV["DATABASE_URL"])
|
21
|
+
establish_connection_without_replidog(spec)
|
22
|
+
proxy_handler.remove_connection(self)
|
23
|
+
proxy_handler.establish_connection(connection_pool.spec, self)
|
24
|
+
end
|
25
|
+
|
26
|
+
def connection_with_replidog
|
27
|
+
if replicated?
|
28
|
+
proxy_handler.retrieve_proxy(self).tap do |proxy|
|
29
|
+
proxy.current_model = self
|
30
|
+
end
|
31
|
+
else
|
32
|
+
connection_without_replidog
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def replicated?
|
37
|
+
connection_config[:replications].present?
|
38
|
+
end
|
39
|
+
|
40
|
+
def using(connection_name, &block)
|
41
|
+
if replicated?
|
42
|
+
_using(connection_name, &block)
|
43
|
+
else
|
44
|
+
if block_given?
|
45
|
+
yield
|
46
|
+
else
|
47
|
+
self
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def connected_with_replidog?
|
53
|
+
if replicated?
|
54
|
+
connection.connected?
|
55
|
+
else
|
56
|
+
connected_without_replidog?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def clear_active_connections_with_replidog!
|
61
|
+
clear_active_connections_without_replidog!
|
62
|
+
proxy_handler.clear_active_slave_connections! if replicated?
|
63
|
+
end
|
64
|
+
|
65
|
+
def clear_reloadable_connections_with_replidog!
|
66
|
+
clear_reloadable_connections_without_replidog!
|
67
|
+
proxy_handler.clear_reloadable_slave_connections! if replicated?
|
68
|
+
end
|
69
|
+
|
70
|
+
def clear_all_connections_with_replidog!
|
71
|
+
clear_all_connections_without_replidog!
|
72
|
+
proxy_handler.clear_all_slave_connections! if replicated?
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def _using(connection_name)
|
78
|
+
if block_given?
|
79
|
+
connection.current_connection_name = connection_name
|
80
|
+
yield
|
81
|
+
else
|
82
|
+
ScopeProxy.new(klass: self, connection_name: connection_name)
|
83
|
+
end
|
84
|
+
ensure
|
85
|
+
connection.current_connection_name = nil if block_given?
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
require "active_record"
|
2
|
+
require "active_support/core_ext/object/try"
|
3
|
+
require "active_support/core_ext/class/attribute"
|
4
|
+
|
5
|
+
module Replidog
|
6
|
+
class Proxy
|
7
|
+
REPLICABLE_METHOD_NAMES = [/^select(?:_\w+)$/]
|
8
|
+
REPLICABLE_METHOD_NAMES_REGEXP = /\A#{Regexp.union(REPLICABLE_METHOD_NAMES)}\z/
|
9
|
+
|
10
|
+
attr_writer :index
|
11
|
+
|
12
|
+
attr_reader :configuration
|
13
|
+
|
14
|
+
def initialize(handler, configuration)
|
15
|
+
@handler = handler
|
16
|
+
@configuration = configuration
|
17
|
+
end
|
18
|
+
|
19
|
+
def current_model
|
20
|
+
Thread.current['replidog.current_model']
|
21
|
+
end
|
22
|
+
|
23
|
+
def current_model=(model)
|
24
|
+
Thread.current['replidog.current_model'] = model.is_a?(ActiveRecord::Base) ? model.class : model
|
25
|
+
end
|
26
|
+
|
27
|
+
def current_connection_name
|
28
|
+
Thread.current['replidog.current_connection_name']
|
29
|
+
end
|
30
|
+
|
31
|
+
def current_connection_name=(connection_name)
|
32
|
+
Thread.current['replidog.current_connection_name'] = connection_name
|
33
|
+
end
|
34
|
+
|
35
|
+
def transaction(options = {}, &block)
|
36
|
+
old = current_connection_name
|
37
|
+
self.current_connection_name ||= :master
|
38
|
+
current_connection.transaction(options, &block)
|
39
|
+
ensure
|
40
|
+
self.current_connection_name = old
|
41
|
+
end
|
42
|
+
|
43
|
+
def connected?
|
44
|
+
current_model.connected_without_replidog? && slave_connection_pool_table.values.any?(&:connected?)
|
45
|
+
end
|
46
|
+
|
47
|
+
def clear_active_slave_connections!
|
48
|
+
slave_connection_pool_table.each_value do |pool|
|
49
|
+
pool.release_connection
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def clear_reloadable_slave_connections!
|
54
|
+
slave_connection_pool_table.each_value do |pool|
|
55
|
+
pool.clear_reloadable_connections!
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def clear_all_slave_connections!
|
60
|
+
slave_connection_pool_table.each_value do |pool|
|
61
|
+
pool.disconnect!
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def enable_query_cache!
|
66
|
+
@handler.enable_query_cache!
|
67
|
+
end
|
68
|
+
|
69
|
+
def enable_query_cache_for_slaves!
|
70
|
+
slave_connection_pool_table.each_value do |pool|
|
71
|
+
pool.connection.enable_query_cache!
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def disable_query_cache!
|
76
|
+
@handler.disable_query_cache!
|
77
|
+
end
|
78
|
+
|
79
|
+
def disable_query_cache_for_slaves!
|
80
|
+
slave_connection_pool_table.values.each do |pool|
|
81
|
+
pool.connection.disable_query_cache!
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def clear_query_cache
|
86
|
+
@handler.clear_query_cache
|
87
|
+
end
|
88
|
+
|
89
|
+
def clear_query_cache_for_slaves
|
90
|
+
slave_connection_pool_table.values.each do |pool|
|
91
|
+
pool.connection.clear_query_cache
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def method_missing(method_name, *args, &block)
|
98
|
+
if current_connection_name
|
99
|
+
current_connection.send(method_name, *args, &block)
|
100
|
+
else
|
101
|
+
connection_by_method_name(method_name).send(method_name, *args, &block)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def respond_to_missing?(method, *args)
|
106
|
+
master_connection.respond_to?(method, *args) || super
|
107
|
+
end
|
108
|
+
|
109
|
+
def connection_by_method_name(method_name)
|
110
|
+
REPLICABLE_METHOD_NAMES_REGEXP === method_name ? slave_connection : master_connection
|
111
|
+
end
|
112
|
+
|
113
|
+
def current_connection
|
114
|
+
if current_connection_name.to_s == "master"
|
115
|
+
master_connection
|
116
|
+
else
|
117
|
+
slave_connection_pool_table[current_connection_name.to_s].try(:connection) or raise_connection_not_found
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def master_connection
|
122
|
+
current_model.connection_without_replidog
|
123
|
+
end
|
124
|
+
|
125
|
+
def slave_connection
|
126
|
+
slave_connection_pool.connection
|
127
|
+
end
|
128
|
+
|
129
|
+
def slave_connection_pool
|
130
|
+
slave_connection_pool_table.values[slave_connection_index]
|
131
|
+
end
|
132
|
+
|
133
|
+
def replicated?
|
134
|
+
replications
|
135
|
+
end
|
136
|
+
|
137
|
+
def replications
|
138
|
+
@configuration.config[:replications] || []
|
139
|
+
end
|
140
|
+
|
141
|
+
def slave_connection_pool_table
|
142
|
+
@slave_connection_pools ||= replications.inject({}) do |table, (name, configuration)|
|
143
|
+
table.merge(name => ConnectionPoolCreater.create(configuration))
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def raise_connection_not_found
|
148
|
+
raise StandardError, "connection #{current_connection_name} is not found"
|
149
|
+
end
|
150
|
+
|
151
|
+
def slave_connection_index
|
152
|
+
index.tap { increment_slave_connection_index }
|
153
|
+
end
|
154
|
+
|
155
|
+
def increment_slave_connection_index
|
156
|
+
self.index = (index + 1) % slave_connection_pool_table.size
|
157
|
+
end
|
158
|
+
|
159
|
+
def index
|
160
|
+
@index ||= rand(slave_connection_pool_table.size)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Creates database connection pool from configuration Hash table.
|
164
|
+
class ConnectionPoolCreater
|
165
|
+
def self.create(*args)
|
166
|
+
new(*args).create
|
167
|
+
end
|
168
|
+
|
169
|
+
def initialize(configuration)
|
170
|
+
@configuration = configuration.dup
|
171
|
+
end
|
172
|
+
|
173
|
+
def create
|
174
|
+
spec =
|
175
|
+
if ActiveRecord::VERSION::MAJOR >= 4
|
176
|
+
if ActiveRecord::VERSION::MINOR < 1
|
177
|
+
ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new(@configuration, {}).spec
|
178
|
+
else
|
179
|
+
ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new({}).spec(@configuration)
|
180
|
+
end
|
181
|
+
else
|
182
|
+
ActiveRecord::Base::ConnectionSpecification::Resolver.new(@configuration, {}).spec
|
183
|
+
end
|
184
|
+
|
185
|
+
ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Replidog
|
2
|
+
class ProxyHandler
|
3
|
+
def initialize
|
4
|
+
@proxies = {}
|
5
|
+
@class_to_proxy = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def establish_connection(configuration, klass)
|
9
|
+
@proxies[configuration] ||= Proxy.new(self, configuration)
|
10
|
+
@class_to_proxy[klass.name] ||= @proxies[configuration]
|
11
|
+
end
|
12
|
+
|
13
|
+
def retrieve_proxy(klass)
|
14
|
+
proxy = @class_to_proxy[klass.name]
|
15
|
+
return proxy if proxy
|
16
|
+
return nil if ActiveRecord::Base == klass
|
17
|
+
retrieve_proxy(klass.superclass)
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove_connection(klass)
|
21
|
+
proxy = @class_to_proxy.delete(klass.name)
|
22
|
+
return nil unless proxy
|
23
|
+
|
24
|
+
@proxies.delete(proxy.configuration)
|
25
|
+
proxy.clear_all_slave_connections!
|
26
|
+
proxy.configuration.config
|
27
|
+
end
|
28
|
+
|
29
|
+
def clear_active_slave_connections!
|
30
|
+
@proxies.each_value do |proxy|
|
31
|
+
proxy.clear_active_slave_connections!
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def clear_reloadable_slave_connections!
|
36
|
+
@proxies.each_value do |proxy|
|
37
|
+
proxy.clear_reloadable_slave_connections!
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def clear_all_slave_connections!
|
42
|
+
@proxies.each_value do |proxy|
|
43
|
+
proxy.clear_all_slave_connections!
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def enable_query_cache!
|
48
|
+
ActiveRecord::Base.connection_handler.connection_pools.each_value do |connection_pool|
|
49
|
+
connection_pool.connection.enable_query_cache!
|
50
|
+
end
|
51
|
+
@proxies.each_value(&:enable_query_cache_for_slaves!)
|
52
|
+
end
|
53
|
+
|
54
|
+
def disable_query_cache!
|
55
|
+
ActiveRecord::Base.connection_handler.connection_pools.each_value do |connection_pool|
|
56
|
+
connection_pool.connection.disable_query_cache!
|
57
|
+
end
|
58
|
+
@proxies.each_value(&:disable_query_cache_for_slaves!)
|
59
|
+
end
|
60
|
+
|
61
|
+
def clear_query_cache
|
62
|
+
ActiveRecord::Base.connection_handler.connection_pools.each_value do |connection_pool|
|
63
|
+
connection_pool.connection.clear_query_cache
|
64
|
+
end
|
65
|
+
@proxies.each_value(&:clear_query_cache_for_slaves)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Replidog
|
2
|
+
class ScopeProxy
|
3
|
+
def initialize(attributes)
|
4
|
+
@klass = attributes[:klass]
|
5
|
+
@connection_name = attributes[:connection_name]
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def method_missing(method_name, *args, &block)
|
11
|
+
result = using { @klass.send(method_name, *args, &block) }
|
12
|
+
if result.respond_to?(:scoped)
|
13
|
+
@klass = result
|
14
|
+
self
|
15
|
+
else
|
16
|
+
result
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def using
|
21
|
+
@klass.using(@connection_name) { yield }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|