fresh_connection 1.0.1 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6404116fed235200999c9c0afb902ddc4068143e
4
- data.tar.gz: f8b3b6b01e646d1b36dac6d42bda1be88c3aaba2
3
+ metadata.gz: dc5e646490d77de912ba3add1040b0acecf88d7d
4
+ data.tar.gz: 1bf31e8b593012fd1ee11835cca259b8739ed9c9
5
5
  SHA512:
6
- metadata.gz: be9babe8024f3e99cd5e4aa6d6858c1bd73b9aca5a82115ae338470141352c3dec9f399135fe18e3590fcbf667bec6396129fd60f9e17f7f8adb33fbdcca36d2
7
- data.tar.gz: 4af029f094f1bce4e3fe53d2f12dba0054cb36ba2aa29dfd1c00b99f406ff46df37196d2fb98170b708eef0f5cdf9339b1e397f3d4f735e3f0f7bb112f946067
6
+ metadata.gz: 51b0eb074205141d96030ad1274229b5ebe7078bd5e0a348850ccc2623d0178edbb3694a7ee098a5bcef3ce32f7e547dfbff0fda1a3f3926439216179d80b601
7
+ data.tar.gz: 7e4f445e3a8acf489d7a726d03c127ab4e6b82e7da28a4deda02228823cdbc55932e2d953ca226ff752905576b5d5e59f6cab1015315900e071095c2fa312a15
data/.travis.yml CHANGED
@@ -1,11 +1,15 @@
1
1
  language: ruby
2
2
  before_install:
3
3
  - gem install bundler
4
+ services:
5
+ - mysql
6
+ - postgresql
4
7
  before_script:
5
- - mysql -e 'create database fresh_connection_test;'
6
- - mysql -e 'create database fresh_connection_test_master;'
7
- - mysql -e 'create database fresh_connection_test_slave;'
8
8
  - bundle update
9
+ - cp test/config/database_postgresql.yml.travis test/config/database_postgresql.yml
10
+ - psql -c 'create database fresh_connection_test_master;' -U postgres
11
+ - psql -c 'create database fresh_connection_test_slave1;' -U postgres
12
+ - psql -c 'create database fresh_connection_test_slave2;' -U postgres
9
13
  cache: bundler
10
14
  rvm:
11
15
  - 2.0.0
@@ -18,6 +22,26 @@ gemfile:
18
22
  - gemfiles/rails40.gemfile
19
23
  - gemfiles/rails41.gemfile
20
24
  - gemfiles/rails42.gemfile
25
+ - gemfiles/rails50.gemfile
26
+ matrix:
27
+ exclude:
28
+ - rvm: 2.3.0
29
+ gemfile: gemfiles/rails3.gemfile
30
+ - rvm: ruby-head
31
+ gemfile: gemfiles/rails3.gemfile
32
+ - rvm: 2.3.0
33
+ gemfile: gemfiles/rails40.gemfile
34
+ - rvm: ruby-head
35
+ gemfile: gemfiles/rails40.gemfile
36
+ - rvm: 2.0.0
37
+ gemfile: gemfiles/rails50.gemfile
38
+ - rvm: 2.1.8
39
+ gemfile: gemfiles/rails50.gemfile
40
+ allow_failures:
41
+ - rvm: ruby-head
42
+ - gemfile: gemfiles/rails50.gemfile
43
+ fast_finish: true
21
44
  notifications:
22
45
  emails:
23
46
  - tsukasa.oishi@gmail.com
47
+ bundler_args: --jobs 3 --retry 3
data/Appraisals CHANGED
@@ -1,19 +1,19 @@
1
- appraise "rails3" do
2
- gem "activerecord", "~> 3.2.0"
3
- gem "activesupport", "~> 3.2.0"
4
- end
5
-
6
1
  appraise "rails40" do
7
- gem "activerecord", "~> 4.0.0"
8
- gem "activesupport", "~> 4.0.0"
2
+ gem 'activerecord', '~> 4.0.0'
3
+ gem 'mysql2', '~> 0.3.10'
9
4
  end
10
5
 
11
6
  appraise "rails41" do
12
- gem "activerecord", "~> 4.1.0"
13
- gem "activesupport", "~> 4.1.0"
7
+ gem 'activerecord', '~> 4.1.0'
8
+ gem 'mysql2', '~> 0.3.13'
14
9
  end
15
10
 
16
11
  appraise "rails42" do
17
- gem "activerecord", "~> 4.2.0"
18
- gem "activesupport", "~> 4.2.0"
12
+ gem 'activerecord', '~> 4.2.0'
13
+ gem 'mysql2', '~> 0.3.13'
14
+ end
15
+
16
+ appraise "rails50" do
17
+ gem 'activerecord', '5.0.0.beta3'
18
+ gem 'mysql2', '>= 0.3.18', '< 0.5'
19
19
  end
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in n_adic_number.gemspec
3
+ # Specify your gem's dependencies in fresh_connection.gemspec
4
4
  gemspec
data/README.md CHANGED
@@ -1,43 +1,42 @@
1
1
  # FreshConnection
2
-
3
- FreshConnection allows access to Mysql slave servers in Rails.
4
-
5
2
  [![Gem Version](https://badge.fury.io/rb/fresh_connection.svg)](http://badge.fury.io/rb/fresh_connection) [![Build Status](https://travis-ci.org/tsukasaoishi/fresh_connection.svg?branch=master)](https://travis-ci.org/tsukasaoishi/fresh_connection) [![Code Climate](https://codeclimate.com/github/tsukasaoishi/fresh_connection/badges/gpa.svg)](https://codeclimate.com/github/tsukasaoishi/fresh_connection)
6
3
 
7
- ActiveRecord can only access a single server by default.
8
- FreshConnection can access to replicated Mysql slave servers via a loadbalancer.
4
+ ActiveRecord accesses a single server by default.
5
+ FreshConnection can access to slave servers via a load balancer.
9
6
 
10
7
  For example.
11
8
  ```
12
- Rails ------------ Mysql(Master)
9
+ Rails ------------ Master DB
13
10
  |
14
- | +------ Mysql(Slave1)
11
+ | +------ Slave1 DB
15
12
  | |
16
13
  +---- Loadbalancer ---+
17
14
  |
18
- +------ Mysql(Slave2)
15
+ +------ Slave2 DB
19
16
  ```
20
17
 
21
- When Rails controller's action begins, FreshConnction connects with one of slave servers behind the loadbalacer.
22
- Read query goes to the slave server via the loadbalancer.
23
- Write query goes to the master server. Inside transaction, all queries go to the master server.
24
- All Mysql connections is disconnected at the end of the Rails controller's action.
18
+ FreshConnction connects with one of slave servers behind the load balancer.
19
+ Read query goes to the slave server.
20
+ Write query goes to the master server.
21
+ Inside transaction, all queries go to the master server.
25
22
 
23
+ If you can't use a load balancer, could use [EbisuConnection](https://github.com/tsukasaoishi/ebisu_connection).
26
24
 
27
25
  ## Usage
28
-
26
+ ### Access to Slave
29
27
  Read query goes to the slave server.
30
28
 
31
29
  ```ruby
32
30
  Article.where(:id => 1)
33
31
  ```
34
32
 
35
- If you want to access to the master server, use read_master.
33
+ ### Access to Master
34
+ If read query want to access to the master server, use `read_master`.
35
+ In before version 0.4.3, can use `readonly(false)`.
36
36
 
37
37
  ```ruby
38
38
  Article.where(:id => 1).read_master
39
39
  ```
40
- If you would like to use readonly(false), use version before 1.0.0.
41
40
 
42
41
  In transaction, All queries go to the master server.
43
42
 
@@ -47,7 +46,7 @@ Article.transaction do
47
46
  end
48
47
  ```
49
48
 
50
- Create, Update. Delete queries go to the master server.
49
+ Create, Update and Delete queries go to the master server.
51
50
 
52
51
  ```ruby
53
52
  article = Article.create(...)
@@ -56,9 +55,14 @@ article.save
56
55
  article.destory
57
56
  ```
58
57
 
58
+ ## Support Rails version
59
+ FreshConnection supports Rails version 4.0 or later.
60
+ If you are using Rails 3.2, could use FreshConnection version 1.0.0 or before.
59
61
 
60
- ## Installation
62
+ ## Support DB
63
+ FreshConnection supports MySQL and PostgreSQL.
61
64
 
65
+ ## Installation
62
66
  Add this line to your application's Gemfile:
63
67
 
64
68
  ```ruby
@@ -100,7 +104,7 @@ production:
100
104
  ```
101
105
 
102
106
  ```slave``` is a config to connect to slave servers.
103
- Others will use the master server setting.
107
+ Others will use the master server settings.
104
108
 
105
109
  ### use multiple slave servers group
106
110
  If you may want to use multiple slave groups, write the config to ```config/database.yml```.
@@ -136,7 +140,7 @@ class AdminUser < ActiveRecord::Base
136
140
  end
137
141
  ```
138
142
 
139
- The model of children class will access to same slave group as the parent.
143
+ The children class will access to same slave group as the parent.
140
144
 
141
145
  ```ruby
142
146
  class Parent < ActiveRecord::Base
@@ -199,12 +203,17 @@ class MySlaveConnection < FreshConnection::AbstractConnectionManager
199
203
  # must return object of ActiveRecord::ConnectionAdapters::Mysql2Adapter
200
204
  end
201
205
 
206
+ def clear_all_connections!
207
+ # called when all connections disconnect
208
+ end
209
+
202
210
  def put_aside!
203
211
  # called when end of Rails controller action
204
212
  end
205
213
 
206
- def recovery(failure_connection, exception)
214
+ def recovery?
207
215
  # called when raise exception to access slave server
216
+ # retry to access when this method return true
208
217
  end
209
218
  end
210
219
  ```
@@ -227,13 +236,11 @@ How to setup your test environment.
227
236
  First of all, you setting the config of the test mysql server in ```spec/database.yml```
228
237
 
229
238
  ```bash
230
- bundle install --path .bundle
231
- bundle exec appraisal install
239
+ ./bin/setup
232
240
  ```
233
241
 
234
242
  This command run the spec suite for all rails versions supported.
235
243
 
236
244
  ```bash
237
- bundle exec appraisal rake test
245
+ ./bin/test
238
246
  ```
239
-
data/Rakefile CHANGED
@@ -1,10 +1,22 @@
1
1
  require "bundler/gem_tasks"
2
2
  require 'rake/testtask'
3
3
 
4
- Rake::TestTask.new do |t|
5
- t.libs.push "test"
6
- t.test_files = FileList['test/**/*_test.rb']
7
- t.verbose = true
4
+ desc 'Run mysql2 and postgresql test'
5
+ task :test do
6
+ Rake::Task["test:mysql2"].invoke
7
+ Rake::Task["test:postgresql"].invoke
8
+ end
9
+
10
+ namespace :test do
11
+ %w(mysql2 postgresql).each do |db|
12
+ Rake::TestTask.new(db) do |t|
13
+ t.libs << "test"
14
+ t.libs << "lib"
15
+ t.test_files = FileList["test/config/prepare_#{db}", 'test/**/*_test.rb']
16
+ t.verbose = false
17
+ t.warning = false
18
+ end
19
+ end
8
20
  end
9
21
 
10
22
  task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "fresh_connection"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install --path .bundle
7
+ bundle exec appraisal install
8
+
9
+ # Do any other automated setup that you need to do here
data/bin/test ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle exec appraisal rake test
@@ -21,10 +21,11 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.required_ruby_version = '>= 2.0'
23
23
 
24
- spec.add_dependency 'activerecord', '>= 3.2.0', '< 5.0'
25
- spec.add_dependency 'activesupport', '>= 3.2.0', '< 5.0'
26
- spec.add_dependency 'mysql2', '~> 0.3.18'
24
+ spec.add_dependency 'activerecord', '>= 4.0.0', '< 5.0'
25
+ spec.add_dependency 'concurrent-ruby', '~> 1.0.0'
27
26
 
27
+ spec.add_development_dependency 'mysql2', '>= 0.3.13', '< 0.5'
28
+ spec.add_development_dependency 'pg', '~> 0.11'
28
29
  spec.add_development_dependency "bundler", ">= 1.3.0", "< 2.0"
29
30
  spec.add_development_dependency "rake", ">= 0.8.7"
30
31
  spec.add_development_dependency 'appraisal'
@@ -3,6 +3,6 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "activerecord", "~> 4.0.0"
6
- gem "activesupport", "~> 4.0.0"
6
+ gem "mysql2", "~> 0.3.10"
7
7
 
8
8
  gemspec :path => "../"
@@ -3,6 +3,6 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "activerecord", "~> 4.1.0"
6
- gem "activesupport", "~> 4.1.0"
6
+ gem "mysql2", "~> 0.3.13"
7
7
 
8
8
  gemspec :path => "../"
@@ -3,6 +3,6 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "activerecord", "~> 4.2.0"
6
- gem "activesupport", "~> 4.2.0"
6
+ gem "mysql2", "~> 0.3.13"
7
7
 
8
8
  gemspec :path => "../"
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "activerecord", "~> 3.2.0"
6
- gem "activesupport", "~> 3.2.0"
5
+ gem "activerecord", "5.0.0.beta3"
6
+ gem "mysql2", ">= 0.3.18", "< 0.5"
7
7
 
8
8
  gemspec :path => "../"
@@ -1,17 +1,8 @@
1
1
  module FreshConnection
2
2
  class AbstractConnectionManager
3
- EXCEPTION_MESSAGE_WHEN_SLAVE_SERVER_DOWN = [
4
- "MySQL server has gone away",
5
- "closed MySQL connection",
6
- "Can't connect to local MySQL server"
7
- ].map{|msg| Regexp.escape(msg)}.join("|")
8
-
9
- private_constant :EXCEPTION_MESSAGE_WHEN_SLAVE_SERVER_DOWN
10
-
11
3
  attr_reader :slave_group
12
4
 
13
5
  def initialize(slave_group = "slave")
14
- @mutex = Mutex.new
15
6
  @slave_group = slave_group.to_s
16
7
  @slave_group = "slave" if @slave_group.empty?
17
8
  end
@@ -28,23 +19,9 @@ module FreshConnection
28
19
  end
29
20
  undef_method :put_aside!
30
21
 
31
- def recovery(failure_connection, exception)
32
- end
33
- undef_method :recovery
34
-
35
- private
36
-
37
- def synchronize
38
- @mutex.synchronize{ yield }
39
- end
40
-
41
- def current_thread_id
42
- Thread.current.object_id
43
- end
44
-
45
- def slave_down_message?(message)
46
- /#{EXCEPTION_MESSAGE_WHEN_SLAVE_SERVER_DOWN}/o === message
22
+ def recovery?
47
23
  end
24
+ undef_method :recovery?
48
25
  end
49
26
  end
50
27
 
@@ -18,6 +18,14 @@ module FreshConnection
18
18
  access_db == :slave
19
19
  end
20
20
 
21
+ def catch_exceptions
22
+ return @catch_exceptions if defined?(@catch_exceptions)
23
+ @catch_exceptions = [ActiveRecord::StatementInvalid]
24
+ @catch_exceptions << ::Mysql2::Error if defined?(::Mysql2)
25
+ @catch_exceptions += [::PG::Error, ::PGError] if defined?(::PG)
26
+ @catch_exceptions
27
+ end
28
+
21
29
  private
22
30
 
23
31
  def switch_to(new_db)
@@ -1,10 +1,8 @@
1
- require 'active_support/core_ext/hash/keys'
2
-
3
1
  module FreshConnection
4
2
  class ConnectionFactory
5
3
  def initialize(group, modify_spec = {})
6
4
  @group = group.to_sym
7
- @modify_spec = modify_spec.symbolize_keys
5
+ @modify_spec = modify_spec
8
6
  end
9
7
 
10
8
  def new_connection
@@ -22,9 +20,8 @@ module FreshConnection
22
20
  end
23
21
 
24
22
  def build_spec
25
- config = ar_spec.config.symbolize_keys
26
- group_config = (config[@group] || {}).symbolize_keys
27
- config.merge(group_config).merge(@modify_spec)
23
+ config = ar_spec.config
24
+ config.merge(config[@group] || {}).merge(@modify_spec)
28
25
  end
29
26
 
30
27
  def ar_spec
@@ -1,43 +1,44 @@
1
- require 'fresh_connection/abstract_connection_manager'
2
- require 'fresh_connection/connection_factory'
1
+ require 'concurrent'
3
2
 
4
3
  module FreshConnection
5
4
  class ConnectionManager < AbstractConnectionManager
5
+ def initialize(*args)
6
+ super
7
+ @connections = Concurrent::Map.new
8
+ end
9
+
6
10
  def slave_connection
7
- synchronize do
8
- slave_connections[current_thread_id] ||= connection_factory.new_connection
11
+ @connections.fetch_or_store(current_thread_id) do |_|
12
+ connection_factory.new_connection
9
13
  end
10
14
  end
11
15
 
12
16
  def put_aside!
13
- synchronize do
14
- if c = slave_connections.delete(current_thread_id)
15
- c.disconnect! rescue nil
16
- end
17
- end
17
+ conn = @connections.delete(current_thread_id)
18
+ return unless conn
19
+ conn && conn.disconnect! rescue nil
18
20
  end
19
21
 
20
22
  def clear_all_connections!
21
- synchronize do
22
- slave_connections.values.each {|c| c.disconnect! rescue nil }
23
- @slave_connections.clear
23
+ @connections.each_value do |conn|
24
+ conn.disconnect! rescue nil
24
25
  end
26
+ @connections.clear
25
27
  end
26
28
 
27
- def recovery(failure_connection, exception)
28
- do_recovery = slave_down_message?(exception.message)
29
- put_aside! if do_recovery
30
- do_recovery
29
+ def recovery?
30
+ put_aside!
31
+ true
31
32
  end
32
33
 
33
34
  private
34
35
 
35
- def slave_connections
36
- @slave_connections ||= {}
37
- end
38
-
39
36
  def connection_factory
40
37
  @connection_factory ||= ConnectionFactory.new(@slave_group)
41
38
  end
39
+
40
+ def current_thread_id
41
+ Thread.current.object_id
42
+ end
42
43
  end
43
44
  end
@@ -0,0 +1,14 @@
1
+ module FreshConnection
2
+ module Extend
3
+ module ArAbstractAdapter
4
+ def self.prepended(base)
5
+ base.send :attr_writer, :slave_group
6
+ end
7
+
8
+ def log(*args)
9
+ args[1] = "[#{@slave_group}] #{args[1]}" if @slave_group
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,32 +1,24 @@
1
- require 'active_support/core_ext/class/attribute'
2
- require 'fresh_connection/slave_connection_handler'
3
- require 'fresh_connection/access_control'
4
-
5
1
  module FreshConnection
6
2
  module Extend
7
3
  module ArBase
8
- def self.extended(base)
9
- base.class_attribute :slave_connection_handler, :instance_writer => false
10
- base.slave_connection_handler = FreshConnection::SlaveConnectionHandler.new
11
- end
12
-
13
- case ActiveRecord::VERSION::MAJOR
14
- when 4
15
- delegate :read_master, to: :all
16
- when 3
17
- delegate :read_master, to: :scoped
18
- end
19
-
20
- def manage_access(slave_access, &block)
21
- if master_db_only?
22
- FreshConnection::AccessControl.force_master_access(&block)
4
+ def connection
5
+ if FreshConnection::AccessControl.slave_access?
6
+ if logger && logger.debug?
7
+ slave_connection.tap{|c| c.slave_group = slave_group }
8
+ else
9
+ slave_connection
10
+ end
23
11
  else
24
- FreshConnection::AccessControl.access(slave_access, &block)
12
+ super
25
13
  end
26
14
  end
27
15
 
16
+ def read_master
17
+ all.read_master
18
+ end
19
+
28
20
  def with_master(&block)
29
- manage_access(false, &block)
21
+ all.manage_access(false, &block)
30
22
  end
31
23
 
32
24
  def establish_fresh_connection(slave_group = nil)
@@ -54,13 +46,19 @@ module FreshConnection
54
46
  slave_connection_handler.put_aside!
55
47
  end
56
48
 
57
- def recovery(failure_connection, exception)
58
- slave_connection_handler.recovery(self, failure_connection, exception)
49
+ def slave_connection_recovery?
50
+ slave_connection_handler.recovery?(self)
59
51
  end
60
52
 
61
53
  def slave_group
62
54
  slave_connection_handler.slave_group(self)
63
55
  end
56
+
57
+ private
58
+
59
+ def slave_connection_handler
60
+ @@slave_connection_handler ||= FreshConnection::SlaveConnectionHandler.new
61
+ end
64
62
  end
65
63
  end
66
64
  end
@@ -1,18 +1,46 @@
1
- require "fresh_connection/extend/ar_relation/for_rails#{ActiveRecord::VERSION::MAJOR}"
2
-
3
1
  module FreshConnection
4
2
  module Extend
5
3
  module ArRelation
6
- def self.prepended(base)
7
- base.__send__(:prepend, ForRails)
4
+ RETRY_LIMIT = 3
5
+ private_constant :RETRY_LIMIT
6
+
7
+ def manage_access(slave_access = enable_slave_access, &block)
8
+ if @klass.master_db_only?
9
+ FreshConnection::AccessControl.force_master_access(&block)
10
+ else
11
+ retry_count = 0
12
+ begin
13
+ FreshConnection::AccessControl.access(slave_access, &block)
14
+ rescue *FreshConnection::AccessControl.catch_exceptions
15
+ if @klass.slave_connection_recovery?
16
+ retry_count += 1
17
+ retry if retry_count < RETRY_LIMIT
18
+ end
19
+
20
+ raise
21
+ end
22
+ end
8
23
  end
9
24
 
10
- def calculate(operation, column_name, options = {})
11
- @klass.manage_access(enable_slave_access) { super }
25
+ def calculate(*args)
26
+ manage_access { super }
12
27
  end
13
28
 
14
29
  def exists?(*args)
15
- @klass.manage_access(enable_slave_access) { super }
30
+ manage_access { super }
31
+ end
32
+
33
+ def pluck(*args)
34
+ manage_access { super }
35
+ end
36
+
37
+ def read_master
38
+ spawn.read_master!
39
+ end
40
+
41
+ def read_master!
42
+ @read_from_master = true
43
+ self
16
44
  end
17
45
 
18
46
  def enable_slave_access
@@ -22,11 +50,7 @@ module FreshConnection
22
50
  private
23
51
 
24
52
  def exec_queries
25
- return @records if loaded?
26
-
27
- @klass.manage_access(enable_slave_access) do
28
- super
29
- end
53
+ manage_access { super }
30
54
  end
31
55
  end
32
56
  end
@@ -2,9 +2,7 @@ module FreshConnection
2
2
  module Extend
3
3
  module ArStatementCache
4
4
  def execute(params, klass, connection)
5
- klass.manage_access(klass.all.enable_slave_access) do
6
- super
7
- end
5
+ klass.all.manage_access { super }
8
6
  end
9
7
  end
10
8
  end
@@ -1,27 +1,18 @@
1
- require 'active_support/lazy_load_hooks'
1
+ require 'active_record'
2
+ require 'fresh_connection/extend/ar_base'
3
+ require 'fresh_connection/extend/ar_relation'
4
+ require 'fresh_connection/extend/ar_abstract_adapter'
2
5
 
3
- ActiveSupport.on_load(:active_record) do
4
- require 'fresh_connection/extend/ar_base'
5
- require 'fresh_connection/extend/ar_relation'
6
- require 'fresh_connection/extend/connection_handler'
7
- require 'fresh_connection/extend/mysql2_adapter'
8
- require 'active_record/connection_adapters/mysql2_adapter'
6
+ module ActiveRecord
7
+ Base.extend FreshConnection::Extend::ArBase
8
+ Relation.send :prepend, FreshConnection::Extend::ArRelation
9
9
 
10
- ActiveRecord::Base.extend FreshConnection::Extend::ArBase
11
- ActiveRecord::Relation.__send__(:prepend, FreshConnection::Extend::ArRelation)
12
-
13
- ActiveRecord::ConnectionAdapters::ConnectionHandler.__send__(
14
- :prepend, FreshConnection::Extend::ConnectionHandler
15
- )
16
-
17
- ActiveRecord::ConnectionAdapters::Mysql2Adapter.__send__(
18
- :prepend, FreshConnection::Extend::Mysql2Adapter
19
- )
20
-
21
- if defined?(ActiveRecord::StatementCache)
10
+ if defined?(StatementCache)
22
11
  require 'fresh_connection/extend/ar_statement_cache'
23
- ActiveRecord::StatementCache.__send__(:prepend, FreshConnection::Extend::ArStatementCache)
12
+ StatementCache.send :prepend, FreshConnection::Extend::ArStatementCache
24
13
  end
25
14
 
26
- ActiveRecord::Base.establish_fresh_connection
15
+ ConnectionAdapters::AbstractAdapter.send :prepend, FreshConnection::Extend::ArAbstractAdapter
16
+
17
+ Base.establish_fresh_connection
27
18
  end
@@ -1,15 +1,17 @@
1
+ require 'concurrent'
2
+
1
3
  module FreshConnection
2
4
  class SlaveConnectionHandler
3
5
  def initialize
4
- @class_to_pool = {}
6
+ @class_to_pool = Concurrent::Map.new
5
7
  end
6
8
 
7
9
  def establish_connection(name, slave_group)
8
- if cm = @class_to_pool[name]
10
+ if cm = class_to_pool[name]
9
11
  cm.put_aside!
10
12
  end
11
13
 
12
- @class_to_pool[name] = FreshConnection.connection_manager.new(slave_group)
14
+ class_to_pool[name] = FreshConnection.connection_manager.new(slave_group)
13
15
  end
14
16
 
15
17
  def connection(klass)
@@ -22,16 +24,16 @@ module FreshConnection
22
24
  end
23
25
  end
24
26
 
27
+ def recovery?(klass)
28
+ detect_connection_manager(klass).recovery?
29
+ end
30
+
25
31
  def put_aside!
26
32
  all_connection_managers do |connection_manager|
27
33
  connection_manager.put_aside!
28
34
  end
29
35
  end
30
36
 
31
- def recovery(klass, failure_connection, exception)
32
- detect_connection_manager(klass).recovery(failure_connection, exception)
33
- end
34
-
35
37
  def slave_group(klass)
36
38
  detect_connection_manager(klass).slave_group
37
39
  end
@@ -39,17 +41,21 @@ module FreshConnection
39
41
  private
40
42
 
41
43
  def all_connection_managers
42
- @class_to_pool.values.each do |connection_manager|
44
+ class_to_pool.values.each do |connection_manager|
43
45
  yield(connection_manager)
44
46
  end
45
47
  end
46
48
 
47
49
  def detect_connection_manager(klass)
48
- c = @class_to_pool[klass.name]
50
+ c = class_to_pool[klass.name]
49
51
  return c if c
50
52
  return nil if ActiveRecord::Base == klass
51
53
  detect_connection_manager(klass.superclass)
52
54
  end
55
+
56
+ def class_to_pool
57
+ @class_to_pool
58
+ end
53
59
  end
54
60
  end
55
61
 
@@ -1,4 +1,4 @@
1
1
  module FreshConnection
2
- VERSION = "1.0.1"
2
+ VERSION = "2.0.0"
3
3
  end
4
4
 
@@ -1,6 +1,11 @@
1
- require 'fresh_connection/connection_manager'
2
-
3
1
  module FreshConnection
2
+ autoload :ConnectionManager, 'fresh_connection/connection_manager'
3
+ autoload :AbstractConnectionManager, 'fresh_connection/abstract_connection_manager'
4
+ autoload :ConnectionManager, 'fresh_connection/connection_manager'
5
+ autoload :ConnectionFactory, 'fresh_connection/connection_factory'
6
+ autoload :SlaveConnectionHandler, 'fresh_connection/slave_connection_handler'
7
+ autoload :AccessControl, 'fresh_connection/access_control'
8
+
4
9
  class << self
5
10
  attr_writer :connection_manager
6
11
 
@@ -11,4 +16,4 @@ module FreshConnection
11
16
  end
12
17
 
13
18
  require 'fresh_connection/extend'
14
- require "fresh_connection/railtie" if defined?(Rails)
19
+ require 'fresh_connection/railtie' if defined?(Rails)
metadata CHANGED
@@ -1,143 +1,157 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fresh_connection
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tsukasa OISHI
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-04-12 00:00:00.000000000 Z
11
+ date: 2016-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 3.2.0
20
- - - <
19
+ version: 4.0.0
20
+ - - "<"
21
21
  - !ruby/object:Gem::Version
22
22
  version: '5.0'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
- - - '>='
27
+ - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: 3.2.0
30
- - - <
29
+ version: 4.0.0
30
+ - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '5.0'
33
33
  - !ruby/object:Gem::Dependency
34
- name: activesupport
34
+ name: concurrent-ruby
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - '>='
37
+ - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 3.2.0
40
- - - <
41
- - !ruby/object:Gem::Version
42
- version: '5.0'
39
+ version: 1.0.0
43
40
  type: :runtime
44
41
  prerelease: false
45
42
  version_requirements: !ruby/object:Gem::Requirement
46
43
  requirements:
47
- - - '>='
48
- - !ruby/object:Gem::Version
49
- version: 3.2.0
50
- - - <
44
+ - - "~>"
51
45
  - !ruby/object:Gem::Version
52
- version: '5.0'
46
+ version: 1.0.0
53
47
  - !ruby/object:Gem::Dependency
54
48
  name: mysql2
55
49
  requirement: !ruby/object:Gem::Requirement
56
50
  requirements:
57
- - - ~>
51
+ - - ">="
58
52
  - !ruby/object:Gem::Version
59
- version: 0.3.18
60
- type: :runtime
53
+ version: 0.3.13
54
+ - - "<"
55
+ - !ruby/object:Gem::Version
56
+ version: '0.5'
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 0.3.13
64
+ - - "<"
65
+ - !ruby/object:Gem::Version
66
+ version: '0.5'
67
+ - !ruby/object:Gem::Dependency
68
+ name: pg
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '0.11'
74
+ type: :development
61
75
  prerelease: false
62
76
  version_requirements: !ruby/object:Gem::Requirement
63
77
  requirements:
64
- - - ~>
78
+ - - "~>"
65
79
  - !ruby/object:Gem::Version
66
- version: 0.3.18
80
+ version: '0.11'
67
81
  - !ruby/object:Gem::Dependency
68
82
  name: bundler
69
83
  requirement: !ruby/object:Gem::Requirement
70
84
  requirements:
71
- - - '>='
85
+ - - ">="
72
86
  - !ruby/object:Gem::Version
73
87
  version: 1.3.0
74
- - - <
88
+ - - "<"
75
89
  - !ruby/object:Gem::Version
76
90
  version: '2.0'
77
91
  type: :development
78
92
  prerelease: false
79
93
  version_requirements: !ruby/object:Gem::Requirement
80
94
  requirements:
81
- - - '>='
95
+ - - ">="
82
96
  - !ruby/object:Gem::Version
83
97
  version: 1.3.0
84
- - - <
98
+ - - "<"
85
99
  - !ruby/object:Gem::Version
86
100
  version: '2.0'
87
101
  - !ruby/object:Gem::Dependency
88
102
  name: rake
89
103
  requirement: !ruby/object:Gem::Requirement
90
104
  requirements:
91
- - - '>='
105
+ - - ">="
92
106
  - !ruby/object:Gem::Version
93
107
  version: 0.8.7
94
108
  type: :development
95
109
  prerelease: false
96
110
  version_requirements: !ruby/object:Gem::Requirement
97
111
  requirements:
98
- - - '>='
112
+ - - ">="
99
113
  - !ruby/object:Gem::Version
100
114
  version: 0.8.7
101
115
  - !ruby/object:Gem::Dependency
102
116
  name: appraisal
103
117
  requirement: !ruby/object:Gem::Requirement
104
118
  requirements:
105
- - - '>='
119
+ - - ">="
106
120
  - !ruby/object:Gem::Version
107
121
  version: '0'
108
122
  type: :development
109
123
  prerelease: false
110
124
  version_requirements: !ruby/object:Gem::Requirement
111
125
  requirements:
112
- - - '>='
126
+ - - ">="
113
127
  - !ruby/object:Gem::Version
114
128
  version: '0'
115
129
  - !ruby/object:Gem::Dependency
116
130
  name: minitest
117
131
  requirement: !ruby/object:Gem::Requirement
118
132
  requirements:
119
- - - '>='
133
+ - - ">="
120
134
  - !ruby/object:Gem::Version
121
135
  version: '0'
122
136
  type: :development
123
137
  prerelease: false
124
138
  version_requirements: !ruby/object:Gem::Requirement
125
139
  requirements:
126
- - - '>='
140
+ - - ">="
127
141
  - !ruby/object:Gem::Version
128
142
  version: '0'
129
143
  - !ruby/object:Gem::Dependency
130
144
  name: minitest-reporters
131
145
  requirement: !ruby/object:Gem::Requirement
132
146
  requirements:
133
- - - '>='
147
+ - - ">="
134
148
  - !ruby/object:Gem::Version
135
149
  version: '0'
136
150
  type: :development
137
151
  prerelease: false
138
152
  version_requirements: !ruby/object:Gem::Requirement
139
153
  requirements:
140
- - - '>='
154
+ - - ">="
141
155
  - !ruby/object:Gem::Version
142
156
  version: '0'
143
157
  description: https://github.com/tsukasaoishi/fresh_connection
@@ -147,32 +161,32 @@ executables: []
147
161
  extensions: []
148
162
  extra_rdoc_files: []
149
163
  files:
150
- - .gitignore
151
- - .travis.yml
164
+ - ".gitignore"
165
+ - ".travis.yml"
152
166
  - Appraisals
153
167
  - CODE_OF_CONDUCT.md
154
168
  - Gemfile
155
169
  - LICENSE.txt
156
170
  - README.md
157
171
  - Rakefile
172
+ - bin/console
173
+ - bin/setup
174
+ - bin/test
158
175
  - fresh_connection.gemspec
159
- - gemfiles/rails3.gemfile
160
176
  - gemfiles/rails40.gemfile
161
177
  - gemfiles/rails41.gemfile
162
178
  - gemfiles/rails42.gemfile
179
+ - gemfiles/rails50.gemfile
163
180
  - lib/fresh_connection.rb
164
181
  - lib/fresh_connection/abstract_connection_manager.rb
165
182
  - lib/fresh_connection/access_control.rb
166
183
  - lib/fresh_connection/connection_factory.rb
167
184
  - lib/fresh_connection/connection_manager.rb
168
185
  - lib/fresh_connection/extend.rb
186
+ - lib/fresh_connection/extend/ar_abstract_adapter.rb
169
187
  - lib/fresh_connection/extend/ar_base.rb
170
188
  - lib/fresh_connection/extend/ar_relation.rb
171
- - lib/fresh_connection/extend/ar_relation/for_rails3.rb
172
- - lib/fresh_connection/extend/ar_relation/for_rails4.rb
173
189
  - lib/fresh_connection/extend/ar_statement_cache.rb
174
- - lib/fresh_connection/extend/connection_handler.rb
175
- - lib/fresh_connection/extend/mysql2_adapter.rb
176
190
  - lib/fresh_connection/rack/connection_management.rb
177
191
  - lib/fresh_connection/railtie.rb
178
192
  - lib/fresh_connection/slave_connection_handler.rb
@@ -188,17 +202,17 @@ require_paths:
188
202
  - lib
189
203
  required_ruby_version: !ruby/object:Gem::Requirement
190
204
  requirements:
191
- - - '>='
205
+ - - ">="
192
206
  - !ruby/object:Gem::Version
193
207
  version: '2.0'
194
208
  required_rubygems_version: !ruby/object:Gem::Requirement
195
209
  requirements:
196
- - - '>='
210
+ - - ">="
197
211
  - !ruby/object:Gem::Version
198
212
  version: '0'
199
213
  requirements: []
200
214
  rubyforge_project:
201
- rubygems_version: 2.0.14
215
+ rubygems_version: 2.5.1
202
216
  signing_key:
203
217
  specification_version: 4
204
218
  summary: FreshConnection supports to connect with Mysql slave servers via Load Balancers.
@@ -1,31 +0,0 @@
1
- module FreshConnection
2
- module Extend
3
- module ArRelation
4
- module ForRails
5
- def pluck(column_name)
6
- if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s)
7
- column_name = "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}"
8
- end
9
-
10
- result = @klass.manage_access(enable_slave_access) do
11
- klass.connection.select_all(select(column_name).arel, nil)
12
- end
13
-
14
- return result if result.nil? || result.empty?
15
-
16
- last_columns = result.last.keys.last
17
-
18
- result.map do |attributes|
19
- klass.type_cast_attribute(last_columns, klass.initialize_attributes(attributes))
20
- end
21
- end
22
-
23
- def read_master
24
- relation = clone
25
- relation.instance_variable_set("@read_from_master", true)
26
- relation
27
- end
28
- end
29
- end
30
- end
31
- end
@@ -1,20 +0,0 @@
1
- module FreshConnection
2
- module Extend
3
- module ArRelation
4
- module ForRails
5
- def pluck(*args)
6
- @klass.manage_access(enable_slave_access) { super }
7
- end
8
-
9
- def read_master
10
- spawn.read_master!
11
- end
12
-
13
- def read_master!
14
- @read_from_master = true
15
- self
16
- end
17
- end
18
- end
19
- end
20
- end
@@ -1,12 +0,0 @@
1
- module FreshConnection
2
- module Extend
3
- module ConnectionHandler
4
- def retrieve_connection(klass)
5
- c = super
6
- c.model_class = klass
7
- c
8
- end
9
- end
10
- end
11
- end
12
-
@@ -1,45 +0,0 @@
1
- require 'fresh_connection/access_control'
2
-
3
- module FreshConnection
4
- module Extend
5
- module Mysql2Adapter
6
- RETRY_LIMIT = 3
7
- private_constant :RETRY_LIMIT
8
-
9
- def self.prepended(base)
10
- base.__send__(:attr_writer, :model_class)
11
- end
12
-
13
- def select_all(arel, name = nil, binds = [])
14
- if FreshConnection::AccessControl.slave_access?
15
- change_connection do
16
- super(arel, "[#{@model_class.slave_group}] #{name}", binds)
17
- end
18
- else
19
- super
20
- end
21
- end
22
-
23
- private
24
-
25
- def change_connection
26
- retry_count = 0
27
- master_connection = @connection
28
- begin
29
- slave_connection = @model_class.slave_connection
30
- @connection = slave_connection.raw_connection
31
- yield
32
- rescue ActiveRecord::StatementInvalid => exception
33
- if @model_class.recovery(slave_connection, exception)
34
- retry_count += 1
35
- retry if retry_count < RETRY_LIMIT
36
- end
37
-
38
- raise
39
- end
40
- ensure
41
- @connection = master_connection
42
- end
43
- end
44
- end
45
- end