database_slave 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 +31 -0
- data/LICENSE +22 -0
- data/README.md +75 -0
- data/TODOLIST +4 -0
- data/database_slave.gemspec +17 -0
- data/lib/database_slave/base.rb +16 -0
- data/lib/database_slave/configuration.rb +21 -0
- data/lib/database_slave/connection_handler.rb +90 -0
- data/lib/database_slave/railtie.rb +21 -0
- data/lib/database_slave/relation.rb +79 -0
- data/lib/database_slave/runtime_registry.rb +40 -0
- data/lib/database_slave/version.rb +3 -0
- data/lib/database_slave.rb +6 -0
- metadata +56 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d29bdcf5bfb9d4ba3ef24383ff365cf3f6476408
|
4
|
+
data.tar.gz: d9229d9c018ac2abf96ace3ff7574666b0e7087e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0b8219a825ccacffba30827b92a38743f8dfb41835380a74f4c1edba88df788002b7a8189cf4012a5b1f89465dc534d30d4d8d0eff8bcde5da98e11aee43008a
|
7
|
+
data.tar.gz: c38189f41332ab653580de3938a688b681e727daa2eb2edd3c5ab62ee72c7ee21d6d6f9639dbdee01946bd95b7ea0f8ed6416bf6b000a2560dd095b75ea37fda
|
data/.gitignore
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
*.rbc
|
2
|
+
capybara-*.html
|
3
|
+
.rspec
|
4
|
+
/log
|
5
|
+
/tmp
|
6
|
+
/db/*.sqlite3
|
7
|
+
/public/system
|
8
|
+
/coverage/
|
9
|
+
/spec/tmp
|
10
|
+
**.orig
|
11
|
+
rerun.txt
|
12
|
+
pickle-email-*.html
|
13
|
+
|
14
|
+
# TODO Comment out these rules if you are OK with secrets being uploaded to the repo
|
15
|
+
config/initializers/secret_token.rb
|
16
|
+
config/secrets.yml
|
17
|
+
|
18
|
+
## Environment normalisation:
|
19
|
+
/.bundle
|
20
|
+
/vendor/bundle
|
21
|
+
|
22
|
+
# these should all be checked in to normalise the environment:
|
23
|
+
# Gemfile.lock, .ruby-version, .ruby-gemset
|
24
|
+
|
25
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
26
|
+
.rvmrc
|
27
|
+
|
28
|
+
# if using bower-rails ignore default bower_components path bower.json files
|
29
|
+
/vendor/assets/bower_components
|
30
|
+
*.bowerrc
|
31
|
+
bower.json
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Hayden Wei
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# Database_slave
|
2
|
+
This gem provides master and slave databases support for Rails applications. It maintains a slave database configuration in config/shards.yml, and treats config/database.yml as master database. Then, you can choose which database you want to use in your ActiveRecord::Relation clauses.
|
3
|
+
|
4
|
+
For example, you can use slave database to execute complicated and time-consuming database queries to balance the performance of master database, or separate read and write database operations.
|
5
|
+
|
6
|
+
# Requirements
|
7
|
+
|
8
|
+
* Ruby >= 2.0.0
|
9
|
+
* Rails >= 3.2.x
|
10
|
+
|
11
|
+
# Preparing
|
12
|
+
|
13
|
+
1. First of all, create a file named **shards.yml** in your Rails config directory,
|
14
|
+
its content is very similar to config/database.yml:
|
15
|
+
|
16
|
+
```
|
17
|
+
development:
|
18
|
+
slave_database1:
|
19
|
+
adapter: mysql2
|
20
|
+
encoding: utf8
|
21
|
+
reconnect: false
|
22
|
+
port : 3306
|
23
|
+
pool: 5
|
24
|
+
username: root
|
25
|
+
password: test
|
26
|
+
host: 127.0.0.1
|
27
|
+
database: books
|
28
|
+
|
29
|
+
slave_database2:
|
30
|
+
adapter: mysql2
|
31
|
+
...
|
32
|
+
...
|
33
|
+
test:
|
34
|
+
slave_database1:
|
35
|
+
...
|
36
|
+
production:
|
37
|
+
slave_database1:
|
38
|
+
...
|
39
|
+
```
|
40
|
+
|
41
|
+
2. Then add following to your settings.yml file:
|
42
|
+
|
43
|
+
```
|
44
|
+
using_slave: true
|
45
|
+
```
|
46
|
+
|
47
|
+
**true** means you can use slave database, **false** means not.
|
48
|
+
|
49
|
+
# Usage
|
50
|
+
|
51
|
+
There are two ways to use slave database:
|
52
|
+
|
53
|
+
1. **Single Use**: Append `using_slave(:slave_database_name)` to each ActiveRecord::Relation clause.
|
54
|
+
|
55
|
+
Example:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
Book.where(id: 5).using_slave(:books_slave_database)
|
59
|
+
```
|
60
|
+
|
61
|
+
2. **With Block**: In this way, all of queries in the block will use slave_database to execute queries.
|
62
|
+
With this you don't need to append `using_slave()` to each queries.
|
63
|
+
|
64
|
+
Example:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
Book.using_slave(:books_slave_database) do
|
68
|
+
books1 = Book.where(id: 9)
|
69
|
+
books2 = Book.where('id > 100')
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
# License
|
74
|
+
|
75
|
+
See LICENSE file.
|
data/TODOLIST
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
path = File.expand_path("../lib", __FILE__)
|
2
|
+
$:.unshift(path) unless $:.include? path
|
3
|
+
require 'database_slave/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = 'database_slave'
|
7
|
+
gem.version = DatabaseSlave::VERSION
|
8
|
+
gem.date = '2015-03-17'
|
9
|
+
gem.summary = "Provide master and slave databases support for Rails applications."
|
10
|
+
gem.description = "Provide master and slave databases support for Rails applications."
|
11
|
+
gem.authors = ["Hayden Wei"]
|
12
|
+
gem.email = 'haidongrun@gmail.com'
|
13
|
+
gem.homepage = 'https://github.com/Gnodiah/database_slave'
|
14
|
+
gem.license = 'MIT'
|
15
|
+
gem.files = `git ls-files`.split("\n")
|
16
|
+
gem.required_ruby_version = '>= 2.0.0'
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module DatabaseSlave
|
2
|
+
module Base
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
cattr_accessor :slave_connections
|
7
|
+
self.slave_connections = []
|
8
|
+
|
9
|
+
class << self
|
10
|
+
delegate :using_slave, to: :scoped
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
ActiveRecord::Base.send(:include, DatabaseSlave::Base)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module DatabaseSlave
|
2
|
+
def self.configurations
|
3
|
+
Configuration.new.config
|
4
|
+
end
|
5
|
+
|
6
|
+
# TODO 错误检查, 边界条件检查
|
7
|
+
class Configuration
|
8
|
+
attr_reader :config
|
9
|
+
|
10
|
+
def initialize(*)
|
11
|
+
@config = database_configuration[Rails.env]
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def database_configuration
|
17
|
+
require 'erb'
|
18
|
+
YAML::load(ERB.new(IO.read("#{Rails.root}/config/shards.yml")).result)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module DatabaseSlave
|
2
|
+
class ConnectionHandler
|
3
|
+
# === Description
|
4
|
+
#
|
5
|
+
# 该方法根据 DatabaseSlave::Configuration 的配置建立所有的数据库从库链接.
|
6
|
+
#
|
7
|
+
# Rails在启动时会调用 ActiveRecord::Base.establish_connection 来建立database.yml连接,
|
8
|
+
# 以上方法定义在 active_record/connection_adapters/abstract/connection_specification.rb
|
9
|
+
#
|
10
|
+
# 故该方法的设计参考了以上方法, 遵循了以上方法的设计逻辑.
|
11
|
+
#
|
12
|
+
# 注意下面方法中 **klass** 的实现:
|
13
|
+
# 因为remove_connection方法需要一个类名作为参数, 然后会在该类名上调用name方法.
|
14
|
+
# 查看源码可知原方法传入的是self, 即当前调用类; 类自带了name方法.
|
15
|
+
#
|
16
|
+
# 同理, 这里我们也应该传入一个类名, 但self不是所期望的, 于是这里使用了
|
17
|
+
#
|
18
|
+
# self.const_set(slave_name.to_s.strip.camelize, Class.new)
|
19
|
+
#
|
20
|
+
# 的方式动态地创建了一个类.
|
21
|
+
#
|
22
|
+
# 此外, 还可以通过定义一个代理类Proxy然后实现name方法的方式来达到目的.
|
23
|
+
#
|
24
|
+
def self.establish_connection
|
25
|
+
ActiveRecord::Base.slave_connections ||= []
|
26
|
+
|
27
|
+
DatabaseSlave.configurations.each do |slave_name, config|
|
28
|
+
adapter_method = "#{config['adapter']}_connection"
|
29
|
+
spec = ActiveRecord::Base::ConnectionSpecification.new(config, adapter_method)
|
30
|
+
klass = self.const_set(slave_name.to_s.strip.camelize, Class.new)
|
31
|
+
|
32
|
+
unless ActiveRecord::Base.respond_to?(spec.adapter_method)
|
33
|
+
raise "AdapterNotFound: database configuration specifies nonexistent #{config['adapter']} adapter"
|
34
|
+
end
|
35
|
+
|
36
|
+
ActiveRecord::Base.slave_connections << klass.name
|
37
|
+
ActiveRecord::Base.remove_connection klass
|
38
|
+
ActiveRecord::Base.connection_handler.establish_connection klass.name, spec
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# === Description
|
44
|
+
#
|
45
|
+
# 当真正要执行一条SQL语句的时候, Rails会调用
|
46
|
+
# ActiveRecord::Base.connection
|
47
|
+
#
|
48
|
+
# 方法到连接池中获取一个数据库连接. 以上方法定义在:
|
49
|
+
# activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
|
50
|
+
#
|
51
|
+
# 而ActiveRecord::Base.connection则调用的是:
|
52
|
+
# ActiveRecord::Base.connection_handler.retrieve_connection(klass)
|
53
|
+
#
|
54
|
+
# 由此可知, 要想获取到的是从库连接而非主库连接, 那么上述方法的klass就需要
|
55
|
+
# 传入之前建立的从库连接的class名而不是默认的self.
|
56
|
+
#
|
57
|
+
# 因此, 我们在这里重写了ActiveRecord::Base.connection方法, 在其中增加了在什么时候应该使用
|
58
|
+
# 从库连接的判断, 并用Module#prepend方法将我们重写后的connection方法加载到ActiveRecord::Base
|
59
|
+
# 的前面, 以便我们重写后的connection方法比ActiveRecord::Base.connection方法先执行.
|
60
|
+
#
|
61
|
+
module Connection
|
62
|
+
def self.prepended(base)
|
63
|
+
class << base
|
64
|
+
prepend ClassMethods
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
module ClassMethods
|
69
|
+
def connection
|
70
|
+
klass = self
|
71
|
+
|
72
|
+
if Settings.using_slave && slave_connection_exists?
|
73
|
+
slave_name = DatabaseSlave::RuntimeRegistry.current_slave_name
|
74
|
+
klass = DatabaseSlave.const_get(slave_name)
|
75
|
+
end
|
76
|
+
|
77
|
+
ActiveRecord::Base.connection_handler.retrieve_connection(klass)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def slave_connection_exists?
|
83
|
+
slave_name = DatabaseSlave::RuntimeRegistry.current_slave_name
|
84
|
+
slave_name && ActiveRecord::Base.slave_connections.include?(slave_name)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
ActiveRecord::Base.send(:prepend, DatabaseSlave::Connection)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module DatabaseSlave
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
# === Description
|
4
|
+
#
|
5
|
+
# 此处参考Rails启动时建立database.yml连接的方式, 来实现
|
6
|
+
# 在Rails启动时就建立好所有的从库链接.
|
7
|
+
#
|
8
|
+
# 参考文件为 activerecord/lib/active_record/railtie.rb
|
9
|
+
# initializer "active_record.initialize_database"
|
10
|
+
#
|
11
|
+
# 在上面的initializer中Rails会调用
|
12
|
+
# ActiveRecord::Base.establish_connection
|
13
|
+
# 来建立database.yml连接.
|
14
|
+
#
|
15
|
+
initializer 'database_slave.initialize_slave_database' do
|
16
|
+
Rails.logger.info "Connecting to slave database specified by shards.yml"
|
17
|
+
|
18
|
+
DatabaseSlave::ConnectionHandler.establish_connection
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module DatabaseSlave
|
2
|
+
module Relation
|
3
|
+
attr_accessor :slave_name
|
4
|
+
|
5
|
+
def initialize(klass, table)
|
6
|
+
slave_name = nil
|
7
|
+
if (slave = ActiveRecord::Relation.class_variable_get(:@@slave_block_given)).present?
|
8
|
+
self.slave_name = slave
|
9
|
+
end
|
10
|
+
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def using_slave?
|
15
|
+
slave_name.to_s.present?
|
16
|
+
end
|
17
|
+
|
18
|
+
def unusing_slave
|
19
|
+
slave_name = nil
|
20
|
+
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def using_slave(slave_name)
|
25
|
+
if Settings.using_slave
|
26
|
+
if block_given?
|
27
|
+
name = "DatabaseSlave::ConnectionHandler::#{slave_name.to_s.strip.camelize}"
|
28
|
+
ActiveRecord::Relation.class_variable_set(:@@slave_block_given, name)
|
29
|
+
begin
|
30
|
+
yield
|
31
|
+
ensure
|
32
|
+
ActiveRecord::Relation.class_variable_set(:@@slave_block_given, nil)
|
33
|
+
DatabaseSlave::RuntimeRegistry.current_slave_name = nil
|
34
|
+
end
|
35
|
+
else
|
36
|
+
self.slave_name = "DatabaseSlave::ConnectionHandler::#{slave_name.to_s.strip.camelize}"
|
37
|
+
relation = clone
|
38
|
+
|
39
|
+
if ActiveRecord::Base.slave_connections.include? self.slave_name
|
40
|
+
relation
|
41
|
+
else
|
42
|
+
raise "#{slave_name} is not exist."
|
43
|
+
end
|
44
|
+
end
|
45
|
+
else
|
46
|
+
clone
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# alias using using_slave
|
51
|
+
|
52
|
+
# === Description
|
53
|
+
#
|
54
|
+
# Rails中所有的relation最后都是调用to_a后返回最终结果.
|
55
|
+
#
|
56
|
+
# 这里我们重写ActiveRecord::Relation的to_a方法只是为了做一件事:
|
57
|
+
#
|
58
|
+
# 必须在当前relation返回后将是否使用从库的标识设置为否,
|
59
|
+
# 以免影响执行下一个relation时的主从库选择错误.
|
60
|
+
#
|
61
|
+
# 对应到代码即:
|
62
|
+
# DatabaseSlave::RuntimeRegistry.current_slave_name = nil
|
63
|
+
#
|
64
|
+
def to_a
|
65
|
+
DatabaseSlave::RuntimeRegistry.current_slave_name = slave_name if using_slave?
|
66
|
+
|
67
|
+
super
|
68
|
+
ensure
|
69
|
+
DatabaseSlave::RuntimeRegistry.current_slave_name = nil
|
70
|
+
end if defined?(Rails)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.prepended(klass)
|
74
|
+
klass.send :prepend, Relation
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
ActiveRecord::Relation.send(:prepend, DatabaseSlave::Relation)
|
79
|
+
ActiveRecord::Relation.class_variable_set(:@@slave_block_given, nil)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# 该类定义拷贝自Rails 4.0.0 的 lib/active_support/per_thread_registry.rb
|
2
|
+
# 因为Rails 3.x 没有该文件.
|
3
|
+
module ActiveSupport
|
4
|
+
module PerThreadRegistry
|
5
|
+
protected
|
6
|
+
|
7
|
+
def method_missing(name, *args, &block) # :nodoc:
|
8
|
+
# Caches the method definition as a singleton method of the receiver.
|
9
|
+
define_singleton_method(name) do |*a, &b|
|
10
|
+
per_thread_registry_instance.public_send(name, *a, &b)
|
11
|
+
end
|
12
|
+
|
13
|
+
send(name, *args, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def per_thread_registry_instance
|
19
|
+
Thread.current[name] ||= new
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module DatabaseSlave
|
25
|
+
# This is a thread locals registry for Active Record. For example:
|
26
|
+
#
|
27
|
+
# ActiveRecord::RuntimeRegistry.connection_handler
|
28
|
+
#
|
29
|
+
# returns the connection handler local to the current thread.
|
30
|
+
#
|
31
|
+
# See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
|
32
|
+
# for further details.
|
33
|
+
# 该类定义拷贝自Rails 4.0.0 的 lib/active_record/runtime_registry.rb
|
34
|
+
# 因为Rails 3.x 没有该文件.
|
35
|
+
class RuntimeRegistry # :nodoc:
|
36
|
+
extend ActiveSupport::PerThreadRegistry
|
37
|
+
|
38
|
+
attr_accessor :current_slave_name
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: database_slave
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Hayden Wei
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-17 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Provide master and slave databases support for Rails applications.
|
14
|
+
email: haidongrun@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- ".gitignore"
|
20
|
+
- LICENSE
|
21
|
+
- README.md
|
22
|
+
- TODOLIST
|
23
|
+
- database_slave.gemspec
|
24
|
+
- lib/database_slave.rb
|
25
|
+
- lib/database_slave/base.rb
|
26
|
+
- lib/database_slave/configuration.rb
|
27
|
+
- lib/database_slave/connection_handler.rb
|
28
|
+
- lib/database_slave/railtie.rb
|
29
|
+
- lib/database_slave/relation.rb
|
30
|
+
- lib/database_slave/runtime_registry.rb
|
31
|
+
- lib/database_slave/version.rb
|
32
|
+
homepage: https://github.com/Gnodiah/database_slave
|
33
|
+
licenses:
|
34
|
+
- MIT
|
35
|
+
metadata: {}
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 2.0.0
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
requirements: []
|
51
|
+
rubyforge_project:
|
52
|
+
rubygems_version: 2.2.2
|
53
|
+
signing_key:
|
54
|
+
specification_version: 4
|
55
|
+
summary: Provide master and slave databases support for Rails applications.
|
56
|
+
test_files: []
|