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 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,4 @@
1
+ * 添加容错处理, 包括检查配置文件等
2
+ * 添加测试代码
3
+ * 需要优化代码: 只允许gem外部代码read slave_name, 不允许write(出于安全考虑)
4
+ * 添加generator自动生成config/shards.yml.example
@@ -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
@@ -0,0 +1,3 @@
1
+ module DatabaseSlave
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,6 @@
1
+ require 'database_slave/base'
2
+ require 'database_slave/configuration'
3
+ require 'database_slave/connection_handler'
4
+ require 'database_slave/runtime_registry'
5
+ require 'database_slave/relation'
6
+ require 'database_slave/railtie'
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: []