ar-multidb 0.1.12 → 0.4.2

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
- SHA1:
3
- metadata.gz: cedb676272cc87f3a21d66c15efd9058e0e13551
4
- data.tar.gz: b1dae22e38cfbe06ab71ccaf7abbe6dbfb459c9f
2
+ SHA256:
3
+ metadata.gz: 3ecd6501582114326efd8452ffe712c79b5667d1ce54a181eff6efeaf40d1897
4
+ data.tar.gz: 85c3e8b3dc155374ef7d7caed121bf5a140cf64030b760bb6cdcdb3123e8cddc
5
5
  SHA512:
6
- metadata.gz: 5662d638ad37ebd926c8a7c369ed60de18f8debbc121cad19c760a9f7301e73b68d1914f0d8920aa9b2e2dcb7a2af01e79b89a1bf65041b5a636b6e239417502
7
- data.tar.gz: cb9a4f11337c379216d295d579e1938266377c887e7b4608a07fbcb1dcc78d298502459f8ad3cfb97e0acda5209261e9a869560e926654d213747e3946e0b65b
6
+ metadata.gz: e2eefe25c89d914367afecce5ebc07f4be668985097969f5a1c9acb563a22c448778b4128caf64e5dd6029437e28b13188097d6c66c111f4b5911543858f50b1
7
+ data.tar.gz: c83c08a921575803b15ebf473e125ad6da3619b321208766e9f8d058bb8226154cd21255d14aa77be260bfd17da29926be8aa3bdeda547fed0c6f46624370283
data/.gitignore CHANGED
@@ -1,4 +1,7 @@
1
1
  /pkg
2
2
  *~
3
3
  \.DS_Store
4
- *.sqlite
4
+ *.sqlite
5
+ Gemfile.lock
6
+ Gemfile.local
7
+ .rspec
@@ -0,0 +1,62 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.4
3
+ Exclude:
4
+ - '**/vendor/**/*'
5
+
6
+ Layout/EmptyLinesAroundAttributeAccessor:
7
+ Enabled: true
8
+ Layout/SpaceAroundMethodCallOperator:
9
+ Enabled: true
10
+ Layout/LineLength:
11
+ Exclude:
12
+ - 'ar-multidb.gemspec'
13
+ Lint/DeprecatedOpenSSLConstant:
14
+ Enabled: true
15
+ Lint/DuplicateElsifCondition:
16
+ Enabled: true
17
+ Lint/MixedRegexpCaptureTypes:
18
+ Enabled: true
19
+ Lint/RaiseException:
20
+ Enabled: true
21
+ Lint/StructNewOverride:
22
+ Enabled: true
23
+ Naming/FileName:
24
+ Exclude:
25
+ - 'lib/ar-multidb.rb'
26
+ Metrics:
27
+ Enabled: false
28
+ Style/AccessorGrouping:
29
+ Enabled: true
30
+ Style/ArrayCoercion:
31
+ Enabled: true
32
+ Style/BisectedAttrAccessor:
33
+ Enabled: true
34
+ Style/CaseLikeIf:
35
+ Enabled: true
36
+ Style/Documentation:
37
+ Enabled: false
38
+ Style/ExponentialNotation:
39
+ Enabled: true
40
+ Style/HashAsLastArrayItem:
41
+ Enabled: true
42
+ Style/HashEachMethods:
43
+ Enabled: true
44
+ Style/HashLikeCase:
45
+ Enabled: true
46
+ Style/HashTransformKeys:
47
+ Enabled: true
48
+ Style/HashTransformValues:
49
+ Enabled: true
50
+ Style/RedundantAssignment:
51
+ Enabled: true
52
+ Style/RedundantFetchBlock:
53
+ Enabled: true
54
+ Style/RedundantFileExtensionInRequire:
55
+ Enabled: true
56
+ Style/RedundantRegexpCharacterClass:
57
+ Enabled: true
58
+ Style/RedundantRegexpEscape:
59
+ Enabled: true
60
+ Style/SlicingWithRange:
61
+ Enabled: true
62
+
@@ -1,3 +1,15 @@
1
- rvm:
2
- - 1.9.3
3
- - 2.0
1
+ sudo: false
2
+ language: ruby
3
+ cache: bundler
4
+
5
+ script:
6
+ - bundle exec rubocop
7
+ - bundle exec rspec
8
+
9
+ matrix:
10
+ fast_finish: true
11
+ include:
12
+ - rvm: 2.5
13
+ gemfile: gemfiles/activerecord51.gemfile
14
+ - rvm: 2.5
15
+ gemfile: gemfiles/activerecord52.gemfile
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
+ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html)
6
+
7
+ ## [0.4.2]
8
+ ### Changed
9
+ - adjust rails restriction to exclude untested rails 6
10
+ - adjust minimum ruby version to 2.4
11
+ - add rubocop
12
+
13
+ ## [0.4.1]
14
+ ### Added
15
+ - Added support for database aliases ( PR #26 )
16
+
data/Gemfile CHANGED
@@ -1,9 +1,12 @@
1
- source "http://rubygems.org"
1
+ # frozen_string_literal: true
2
+
3
+ source 'http://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in ar-multidb.gemspec
4
6
  gemspec
5
7
 
6
- group :test do
7
- # For travis-ci.org
8
- gem "rake"
8
+ local_gemfile = 'Gemfile.local'
9
+
10
+ if File.exist?(local_gemfile)
11
+ eval(File.read(local_gemfile)) # rubocop:disable Security/Eval
9
12
  end
@@ -2,9 +2,9 @@
2
2
 
3
3
  # Multidb
4
4
 
5
- A simple, no-nonsense ActiveRecord extension which allows the application to switch between multiple database connections, such as in a master/slave environment. For example:
5
+ A simple, no-nonsense ActiveRecord extension which allows the application to switch between multiple database connections, such as in a primary/replica environment. For example:
6
6
 
7
- Multidb.use(:slave) do
7
+ Multidb.use(:replica) do
8
8
  @posts = Post.all
9
9
  end
10
10
 
@@ -14,8 +14,13 @@ Randomized balancing of multiple connections within a group is supported. In the
14
14
 
15
15
  ## Requirements
16
16
 
17
- * Ruby 1.9.3 or later.
18
- * ActiveRecord 3.0 or later. (Earlier versions can use the gem version 0.1.10.)
17
+ * Ruby 2.4 or later.
18
+ * ActiveRecord 5.1 or later.
19
+
20
+ ## Older releases
21
+ For ActiveRecord 4. through 5.0 use version 0.3
22
+ For ActiveRecord older than 4.0 use the gem version 0.1.13
23
+ For ActiveRecord older than 3.0 use 0.1.10
19
24
 
20
25
  ## Comparison to other ActiveRecord extensions
21
26
 
@@ -27,10 +32,10 @@ Compared to other, more full-featured extensions such as Octopus and Seamless Da
27
32
 
28
33
  **Orthogonal**. Unlike Octopus, for example, connections follow context:
29
34
 
30
- Multidb.use(:master) do
35
+ Multidb.use(:primary) do
31
36
  @post = Post.find(1)
32
- Multidb.use(:slave) do
33
- @post.authors # This will use the slave
37
+ Multidb.use(:replica) do
38
+ @post.authors # This will use the replica
34
39
  end
35
40
  end
36
41
 
@@ -55,8 +60,8 @@ All that is needed is to set up your `database.yml` file:
55
60
  host: db1
56
61
  multidb:
57
62
  databases:
58
- slave:
59
- host: db-slave
63
+ replica:
64
+ host: db-replica
60
65
 
61
66
  Each database entry may be a hash or an array. So this also works:
62
67
 
@@ -68,24 +73,41 @@ Each database entry may be a hash or an array. So this also works:
68
73
  host: db1
69
74
  multidb:
70
75
  databases:
71
- slave:
72
- - host: db-slave1
73
- - host: db-slave2
76
+ replica:
77
+ - host: db-replica1
78
+ - host: db-replica2
74
79
 
75
80
  If multiple elements are specified, Multidb will use the list to pick a random candidate connection.
76
81
 
77
82
  The database hashes follow the same format as the top-level adapter configuration. In other words, each database connection may override the adapter, database name, username and so on.
78
83
 
84
+ You may also add an "alias" record to the configuration to support more than one name for a given database configuration.
85
+
86
+ production:
87
+ adapter: postgresql
88
+ database: myapp_production
89
+ username: ohoh
90
+ password: mymy
91
+ host: db1
92
+ multidb:
93
+ databases:
94
+ main_db:
95
+ host: db1-a
96
+ secondary_db:
97
+ alias: main_db
98
+
99
+ With the above, `Multidb.use(:main_db)` and `Multidb.use(:secondary_db)` will work identically. This can be useful to support naming scheme migrations transparently: once your application is updated to use `secondary_db` where necessary, you can swap out the configuration.
100
+
79
101
  To use the connection, modify your code by wrapping database access logic in blocks:
80
102
 
81
- Multidb.use(:slave) do
103
+ Multidb.use(:replica) do
82
104
  @posts = Post.all
83
105
  end
84
106
 
85
107
  To wrap entire controller requests, for example:
86
108
 
87
109
  class PostsController < ApplicationController
88
- around_filter :run_using_slave, only: [:index]
110
+ around_filter :run_using_replica, only: [:index]
89
111
 
90
112
  def index
91
113
  @posts = Post.all
@@ -95,22 +117,22 @@ To wrap entire controller requests, for example:
95
117
  # Won't be wrapped
96
118
  end
97
119
 
98
- def run_using_slave(&block)
99
- Multidb.use(:slave, &block)
120
+ def run_using_replica(&block)
121
+ Multidb.use(:replica, &block)
100
122
  end
101
123
  end
102
124
 
103
125
  You can also set the current connection for the remainder of the thread's execution:
104
126
 
105
- Multidb.use(:slave)
127
+ Multidb.use(:replica)
106
128
  # Do work
107
- Multidb.use(:master)
129
+ Multidb.use(:primary)
108
130
 
109
131
  Note that the symbol `:default` will (unless you override it) refer to the default top-level ActiveRecord configuration.
110
132
 
111
133
  ## Development mode
112
134
 
113
- In development you will typically want `Multidb.use(:slave)` to still work, but you probably don't want to run multiple databases on your development box. To make `use` silently fall back to using the default connection, Multidb can run in fallback mode.
135
+ In development you will typically want `Multidb.use(:replica)` to still work, but you probably don't want to run multiple databases on your development box. To make `use` silently fall back to using the default connection, Multidb can run in fallback mode.
114
136
 
115
137
  If you are using Rails, this will be automatically enabled in `development` and `test` environments. Otherwise, simply set `fallback: true` in `database.yml`:
116
138
 
data/Rakefile CHANGED
@@ -1,26 +1,27 @@
1
- require "bundler/gem_tasks"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
2
4
  require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new
5
7
 
6
- task :default => :spec
8
+ task default: :spec
7
9
 
8
10
  desc 'Bump version'
9
11
  task :bump do
10
- if `git status -uno -s --porcelain | wc -l`.to_i > 0
11
- abort "You have uncommitted changed."
12
- end
12
+ abort 'You have uncommitted changed.' if `git status -uno -s --porcelain | wc -l`.to_i.positive?
13
+
13
14
  text = File.read('lib/multidb/version.rb')
14
15
  if text =~ /VERSION = '(.*)'/
15
- old_version = $1
16
+ old_version = Regexp.last_match(1)
16
17
  version_parts = old_version.split('.')
17
18
  version_parts[-1] = version_parts[-1].to_i + 1
18
19
  new_version = version_parts.join('.')
19
20
  text.gsub!(/VERSION = '(.*)'/, "VERSION = '#{new_version}'")
20
21
  File.open('lib/multidb/version.rb', 'w') { |f| f << text }
21
- (system("git add lib/multidb/version.rb") and
22
- system("git commit -m 'Bump to #{new_version}.'")) or abort "Failed to commit."
22
+ (system('git add lib/multidb/version.rb') &&
23
+ system("git commit -m 'Bump to #{new_version}.'")) || abort('Failed to commit.')
23
24
  else
24
- abort "Could not find version number"
25
+ abort 'Could not find version number'
25
26
  end
26
27
  end
@@ -1,25 +1,28 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "multidb/version"
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
+ require 'multidb/version'
4
5
 
5
6
  Gem::Specification.new do |s|
6
- s.name = "ar-multidb"
7
+ s.name = 'ar-multidb'
7
8
  s.version = Multidb::VERSION
8
- s.authors = ["Alexander Staubo"]
9
- s.email = ["alex@bengler.no"]
10
- s.homepage = ""
11
- s.summary = s.description = %q{Multidb is an ActiveRecord extension for switching between multiple database connections, such as master/slave setups.}
12
-
13
- s.rubyforge_project = "ar-multidb"
9
+ s.authors = ['Alexander Staubo', 'Edward Rudd']
10
+ s.email = ['alex@bengler.no', 'urkle@outoforder.cc']
11
+ s.homepage = ''
12
+ s.summary = s.description = 'Multidb is an ActiveRecord extension for switching between multiple database connections, such as primary/replica setups.'
14
13
 
15
14
  s.files = `git ls-files`.split("\n")
16
15
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
- s.require_paths = ["lib"]
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
17
+ s.require_paths = ['lib']
18
+
19
+ s.required_ruby_version = '>= 2.4.0'
19
20
 
20
- s.add_runtime_dependency 'activesupport', '>= 3.0'
21
- s.add_runtime_dependency 'activerecord', '>= 3.0'
21
+ s.add_runtime_dependency 'activerecord', '>= 5.1', '< 6.0'
22
+ s.add_runtime_dependency 'activesupport', '>= 5.1', '< 6.0'
22
23
 
23
- s.add_development_dependency 'rspec'
24
- s.add_development_dependency 'sqlite3'
24
+ s.add_development_dependency 'rake', '~> 12.0'
25
+ s.add_development_dependency 'rspec', '~> 3.8'
26
+ s.add_development_dependency 'rubocop', '~> 0.88.0'
27
+ s.add_development_dependency 'sqlite3', '~> 1.3'
25
28
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'activerecord', '~> 5.1.0'
6
+
7
+ gemspec path: '..'
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'activerecord', '~> 5.2.0'
6
+
7
+ gemspec path: '..'
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'multidb'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record'
2
4
 
3
5
  require 'active_support/core_ext/module/delegation'
@@ -6,5 +8,7 @@ require 'active_support/core_ext/module/aliasing'
6
8
 
7
9
  require_relative 'multidb/configuration'
8
10
  require_relative 'multidb/model_extensions'
11
+ require_relative 'multidb/log_subscriber'
12
+ require_relative 'multidb/candidate'
9
13
  require_relative 'multidb/balancer'
10
14
  require_relative 'multidb/version'
@@ -1,107 +1,107 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Multidb
4
+ class Balancer
5
+ attr_accessor :fallback
2
6
 
3
- class Candidate
4
- def initialize(target)
5
- if target.is_a?(Hash)
6
- adapter = target[:adapter]
7
- begin
8
- require "active_record/connection_adapters/#{adapter}_adapter"
9
- rescue LoadError
10
- raise "Please install the #{adapter} adapter: `gem install activerecord-#{adapter}-adapter` (#{$!})"
11
- end
12
- if defined?(ActiveRecord::ConnectionAdapters::ConnectionSpecification)
13
- spec_class = ActiveRecord::ConnectionAdapters::ConnectionSpecification
14
- else
15
- spec_class = ActiveRecord::Base::ConnectionSpecification
16
- end
17
- @connection_pool = ActiveRecord::ConnectionAdapters::ConnectionPool.new(
18
- spec_class.new(target, "#{adapter}_connection"))
19
- else
20
- @connection_pool = target
21
- end
22
- end
7
+ def initialize(configuration)
8
+ @candidates = {}.with_indifferent_access
9
+ @default_configuration = configuration
23
10
 
24
- def connection(&block)
25
- if block_given?
26
- @connection_pool.with_connection(&block)
27
- else
28
- @connection_pool.connection
29
- end
30
- end
11
+ return unless @default_configuration
31
12
 
32
- attr_reader :connection_pool
33
- end
13
+ append(@default_configuration.raw_configuration[:databases] || {})
34
14
 
35
- class Balancer
15
+ @fallback = if @default_configuration.raw_configuration.include?(:fallback)
16
+ @default_configuration.raw_configuration[:fallback]
17
+ elsif defined?(Rails)
18
+ %w[development test].include?(Rails.env)
19
+ else
20
+ false
21
+ end
36
22
 
37
- def initialize(configuration)
38
- @candidates = {}.with_indifferent_access
39
- @configuration = configuration
40
- if @configuration
41
- (@configuration.raw_configuration[:databases] || {}).each_pair do |name, config|
42
- configs = config.is_a?(Array) ? config : [config]
43
- configs.each do |config|
44
- candidate = Candidate.new(@configuration.default_adapter.merge(config))
45
- @candidates[name] ||= []
46
- @candidates[name].push(candidate)
23
+ @default_candidate = Candidate.new('default', @default_configuration.default_handler)
24
+
25
+ @candidates[:default] = [@default_candidate] unless @candidates.include?(:default)
26
+ end
27
+
28
+ def append(databases)
29
+ databases.each_pair do |name, config|
30
+ configs = config.is_a?(Array) ? config : [config]
31
+ configs.each do |cfg|
32
+ if cfg['alias']
33
+ @candidates[name] = @candidates[cfg['alias']]
34
+ next
47
35
  end
48
- end
49
- if @configuration.raw_configuration.include?(:fallback)
50
- @fallback = @configuration.raw_configuration[:fallback]
51
- elsif defined?(Rails)
52
- @fallback = %w(development test).include?(Rails.env)
53
- else
54
- @fallback = false
55
- end
56
- @default_candidate = Candidate.new(@configuration.default_pool)
57
- unless @candidates.include?(:default)
58
- @candidates[:default] = [@default_candidate]
36
+
37
+ candidate = Candidate.new(name, @default_configuration.default_adapter.merge(cfg))
38
+ @candidates[name] ||= []
39
+ @candidates[name].push(candidate)
59
40
  end
60
41
  end
61
42
  end
62
43
 
63
44
  def disconnect!
64
- @candidates.values.flatten.each do |candidate|
65
- candidate.connection_pool.disconnect!
66
- end
45
+ @candidates.values.flatten.each(&:disconnect!)
67
46
  end
68
47
 
69
- def get(name, &block)
48
+ def get(name, &_block)
70
49
  candidates = @candidates[name]
71
50
  candidates ||= @fallback ? @candidates[:default] : []
51
+
72
52
  raise ArgumentError, "No such database connection '#{name}'" if candidates.empty?
73
- candidate = candidates.respond_to?(:sample) ?
74
- candidates.sample : candidates[rand(candidates.length)]
53
+
54
+ candidate = candidates.respond_to?(:sample) ? candidates.sample : candidates[rand(candidates.length)]
55
+
75
56
  block_given? ? yield(candidate) : candidate
76
57
  end
77
58
 
78
- def use(name, &block)
59
+ def use(name, &_block)
79
60
  result = nil
80
61
  get(name) do |candidate|
81
62
  if block_given?
82
63
  candidate.connection do |connection|
83
- previous_connection, Thread.current[:multidb_connection] =
84
- Thread.current[:multidb_connection], connection
64
+ previous_configuration = Thread.current[:multidb]
65
+ Thread.current[:multidb] = {
66
+ connection: connection,
67
+ connection_name: name
68
+ }
85
69
  begin
86
70
  result = yield
71
+ result = result.to_a if result.is_a?(ActiveRecord::Relation)
87
72
  ensure
88
- Thread.current[:multidb_connection] = previous_connection
73
+ Thread.current[:multidb] = previous_configuration
89
74
  end
90
75
  result
91
76
  end
92
77
  else
93
- result = Thread.current[:multidb_connection] = candidate.connection
78
+ Thread.current[:multidb] = {
79
+ connection: candidate.connection,
80
+ connection_name: name
81
+ }
82
+ result = candidate.connection
94
83
  end
95
84
  end
96
85
  result
97
86
  end
98
87
 
99
88
  def current_connection
100
- Thread.current[:multidb_connection] || @default_candidate.connection
89
+ if Thread.current[:multidb]
90
+ Thread.current[:multidb][:connection]
91
+ else
92
+ @default_candidate.connection
93
+ end
94
+ end
95
+
96
+ def current_connection_name
97
+ if Thread.current[:multidb]
98
+ Thread.current[:multidb][:connection_name]
99
+ else
100
+ :default
101
+ end
101
102
  end
102
103
 
103
104
  class << self
104
- delegate :use, :current_connection, :disconnect!, to: :balancer
105
105
  def use(name, &block)
106
106
  Multidb.balancer.use(name, &block)
107
107
  end
@@ -110,11 +110,13 @@ module Multidb
110
110
  Multidb.balancer.current_connection
111
111
  end
112
112
 
113
+ def current_connection_name
114
+ Multidb.balancer.current_connection_name
115
+ end
116
+
113
117
  def disconnect!
114
118
  Multidb.balancer.disconnect!
115
119
  end
116
120
  end
117
-
118
121
  end
119
-
120
- end
122
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Multidb
4
+ class Candidate
5
+ def initialize(name, target)
6
+ @name = name
7
+
8
+ case target
9
+ when Hash
10
+ @connection_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
11
+ @connection_handler.establish_connection(target.merge(name: 'primary'))
12
+ when ActiveRecord::ConnectionAdapters::ConnectionHandler
13
+ @connection_handler = target
14
+ else
15
+ raise ArgumentError, 'Connection handler not passed to target'
16
+ end
17
+ end
18
+
19
+ def connection(&block)
20
+ if block_given?
21
+ @connection_handler.retrieve_connection_pool('primary').with_connection(&block)
22
+ else
23
+ @connection_handler.retrieve_connection('primary')
24
+ end
25
+ end
26
+
27
+ def disconnect!
28
+ @connection_handler.clear_all_connections!
29
+ end
30
+
31
+ attr_reader :name
32
+ end
33
+ end
@@ -1,54 +1,35 @@
1
- module Multidb
2
-
3
- mattr_reader :configuration
1
+ # frozen_string_literal: true
4
2
 
3
+ module Multidb
5
4
  class << self
6
5
  delegate :use, :get, :disconnect!, to: :balancer
7
6
  end
8
7
 
8
+ def self.init(config)
9
+ activerecord_config = config.dup.with_indifferent_access
10
+ default_adapter = activerecord_config
11
+ configuration_hash = activerecord_config.delete(:multidb)
12
+
13
+ @balancer = Balancer.new(Configuration.new(default_adapter, configuration_hash || {}))
14
+ end
15
+
9
16
  def self.balancer
10
- @balancer ||= create_balancer
17
+ @balancer || raise(NotInitializedError, 'Balancer not initialized. You need to run Multidb.init first')
11
18
  end
12
19
 
13
20
  def self.reset!
14
- @balancer, @configuration = nil, nil
21
+ @balancer = nil
15
22
  end
16
23
 
24
+ class NotInitializedError < StandardError; end
25
+
17
26
  class Configuration
18
27
  def initialize(default_adapter, configuration_hash)
19
- @default_pool = ActiveRecord::Base.connection_pool
28
+ @default_handler = ActiveRecord::Base.connection_handler
20
29
  @default_adapter = default_adapter
21
30
  @raw_configuration = configuration_hash
22
31
  end
23
32
 
24
- attr_reader :default_pool
25
- attr_reader :default_adapter
26
- attr_reader :raw_configuration
33
+ attr_reader :default_handler, :default_adapter, :raw_configuration
27
34
  end
28
-
29
- private
30
-
31
- def self.create_balancer
32
- unless @configuration
33
- begin
34
- connection_pool = ActiveRecord::Base.connection_pool
35
- rescue ActiveRecord::ConnectionNotEstablished
36
- # Ignore
37
- else
38
- connection = connection_pool.connection
39
-
40
- # FIXME: This is hacky, but apparently the only way to get at
41
- # the internal configuration hash.
42
- activerecord_config = connection.instance_variable_get(:@config).dup.with_indifferent_access
43
-
44
- default_adapter, configuration_hash = activerecord_config, activerecord_config.delete(:multidb)
45
-
46
- @configuration = Configuration.new(default_adapter, configuration_hash || {})
47
- end
48
- end
49
- if @configuration
50
- Balancer.new(@configuration)
51
- end
52
- end
53
-
54
- end
35
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Multidb
4
+ module LogSubscriberExtension
5
+ def sql(event)
6
+ name = Multidb.balancer.current_connection_name
7
+ event.payload[:db_name] = name if name
8
+ super
9
+ end
10
+
11
+ def debug(msg)
12
+ name = Multidb.balancer.current_connection_name
13
+ if name
14
+ db = color("[DB: #{name}]", ActiveSupport::LogSubscriber::GREEN, true)
15
+ super(db + msg)
16
+ else
17
+ super
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ ActiveRecord::LogSubscriber.prepend(Multidb::LogSubscriberExtension)
@@ -1,24 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record/base'
4
+
1
5
  module Multidb
2
- module ModelExtensions
6
+ module Connection
7
+ def establish_connection(spec = nil)
8
+ super(spec)
9
+ Multidb.init(connection_pool.spec.config)
10
+ end
11
+
12
+ def connection
13
+ Multidb.balancer.current_connection
14
+ rescue Multidb::NotInitializedError
15
+ super
16
+ end
17
+ end
3
18
 
19
+ module ModelExtensions
4
20
  extend ActiveSupport::Concern
5
21
 
6
22
  included do
7
23
  class << self
8
- alias_method_chain :connection, :multidb
9
- end
10
- end
11
-
12
- module ClassMethods
13
- def connection_with_multidb
14
- if (balancer = Multidb.balancer)
15
- balancer.current_connection
16
- else
17
- connection_without_multidb
18
- end
24
+ prepend Multidb::Connection
19
25
  end
20
26
  end
21
-
22
27
  end
23
28
  end
24
29
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Multidb
2
- VERSION = '0.1.12'
3
- end
4
+ VERSION = '0.4.2'
5
+ end
@@ -1,16 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'spec_helper'
2
4
 
3
5
  describe 'Multidb.balancer' do
4
-
5
6
  context 'with no configuration' do
6
- it 'returns nothing' do
7
- Multidb.balancer.should eq nil
7
+ it 'raises exception' do
8
+ -> { Multidb.balancer }.should raise_error(Multidb::NotInitializedError)
8
9
  end
9
10
  end
10
11
 
11
12
  context 'with configuration' do
12
13
  before do
13
- ActiveRecord::Base.establish_connection(configuration_with_slaves)
14
+ ActiveRecord::Base.establish_connection(configuration_with_replicas)
14
15
  end
15
16
 
16
17
  it 'returns balancer' do
@@ -26,25 +27,54 @@ describe 'Multidb.balancer' do
26
27
 
27
28
  Multidb.balancer.current_connection.should eq conn
28
29
  end
29
- end
30
30
 
31
- describe '#use' do
32
- context 'with no configuration' do
33
- it 'raises exception' do
34
- -> {
35
- Multidb.use(:something) do
36
- end
37
- }.should raise_error(ArgumentError)
31
+ it 'returns default connection name for default connection' do
32
+ ActiveRecord::Base.connection
33
+
34
+ Multidb.balancer.current_connection_name.should eq :default
35
+ end
36
+
37
+ context 'with additional configurations' do
38
+ before do
39
+ additional_configuration = { replica4: { database: 'spec/test-replica4.sqlite' } }
40
+ Multidb.balancer.append(additional_configuration)
41
+ end
42
+
43
+ it 'makes the new database available' do
44
+ Multidb.use(:replica4) do
45
+ conn = ActiveRecord::Base.connection
46
+ conn.should eq Multidb.balancer.current_connection
47
+ list = conn.execute('pragma database_list')
48
+ list.length.should eq 1
49
+ File.basename(list[0]['file']).should eq 'test-replica4.sqlite'
50
+ end
51
+ end
52
+
53
+ it 'returns the connection name' do
54
+ Multidb.use(:replica4) do
55
+ Multidb.balancer.current_connection_name.should eq :replica4
56
+ end
38
57
  end
39
58
  end
59
+ end
40
60
 
61
+ describe '#use' do
41
62
  context 'with configuration' do
42
63
  before do
43
- ActiveRecord::Base.establish_connection(configuration_with_slaves)
64
+ ActiveRecord::Base.establish_connection(configuration_with_replicas)
65
+ end
66
+
67
+ context 'undefined connection' do
68
+ it 'raises exception' do
69
+ lambda {
70
+ Multidb.use(:something) do
71
+ end
72
+ }.should raise_error(ArgumentError)
73
+ end
44
74
  end
45
75
 
46
76
  it 'returns default connection on :default' do
47
- conn = ActiveRecord::Base.connection
77
+ ActiveRecord::Base.connection
48
78
  Multidb.use(:default) do
49
79
  conn2 = ActiveRecord::Base.connection
50
80
  conn2.should eq Multidb.balancer.current_connection
@@ -55,61 +85,75 @@ describe 'Multidb.balancer' do
55
85
  end
56
86
  end
57
87
 
58
- it 'returns slave connection' do
59
- Multidb.use(:slave1) do
88
+ it 'returns replica connection' do
89
+ Multidb.use(:replica1) do
60
90
  conn = ActiveRecord::Base.connection
61
91
  conn.should eq Multidb.balancer.current_connection
62
92
  list = conn.execute('pragma database_list')
63
93
  list.length.should eq 1
64
- File.basename(list[0]['file']).should eq 'test-slave1.sqlite'
94
+ File.basename(list[0]['file']).should eq 'test-replica1.sqlite'
95
+ end
96
+ end
97
+
98
+ it 'returns results instead of relation' do
99
+ class FooBar < ActiveRecord::Base; end
100
+ res = Multidb.use(:replica1) do
101
+ ActiveRecord::Migration.verbose = false
102
+ ActiveRecord::Schema.define(version: 1) { create_table :foo_bars }
103
+ FooBar.where(id: 42)
65
104
  end
105
+ res.should eq []
66
106
  end
67
107
 
68
- it 'returns supports nested slave connection' do
69
- Multidb.use(:slave1) do
70
- Multidb.use(:slave2) do
108
+ it 'returns supports nested replica connection' do
109
+ Multidb.use(:replica1) do
110
+ Multidb.use(:replica2) do
71
111
  conn = ActiveRecord::Base.connection
72
112
  conn.should eq Multidb.balancer.current_connection
73
113
  list = conn.execute('pragma database_list')
74
114
  list.length.should eq 1
75
- File.basename(list[0]['file']).should eq 'test-slave2.sqlite'
115
+ File.basename(list[0]['file']).should eq 'test-replica2.sqlite'
76
116
  end
77
117
  end
78
118
  end
79
119
 
80
120
  it 'returns preserves state when nesting' do
81
- Multidb.use(:slave1) do
82
- Multidb.use(:slave2) do
121
+ Multidb.use(:replica1) do
122
+ Multidb.use(:replica2) do
83
123
  conn = ActiveRecord::Base.connection
84
124
  conn.should eq Multidb.balancer.current_connection
85
125
  list = conn.execute('pragma database_list')
86
126
  list.length.should eq 1
87
- File.basename(list[0]['file']).should eq 'test-slave2.sqlite'
127
+ File.basename(list[0]['file']).should eq 'test-replica2.sqlite'
88
128
  end
89
129
 
90
130
  conn = ActiveRecord::Base.connection
91
131
  conn.should eq Multidb.balancer.current_connection
92
132
  list = conn.execute('pragma database_list')
93
133
  list.length.should eq 1
94
- File.basename(list[0]['file']).should eq 'test-slave1.sqlite'
134
+ File.basename(list[0]['file']).should eq 'test-replica1.sqlite'
95
135
  end
96
136
  end
97
137
 
138
+ it 'returns the parent connection for aliases' do
139
+ Multidb.use(:replica1).should_not eq Multidb.use(:replica_alias)
140
+ Multidb.use(:replica2).should eq Multidb.use(:replica_alias)
141
+ end
142
+
98
143
  it 'returns random candidate' do
99
144
  names = []
100
145
  100.times do
101
- Multidb.use(:slave3) do
146
+ Multidb.use(:replica3) do
102
147
  list = ActiveRecord::Base.connection.execute('pragma database_list')
103
148
  list.length.should eq 1
104
149
  names.push(File.basename(list[0]['file']))
105
150
  end
106
151
  end
107
152
  names.sort.uniq.should eq [
108
- 'test-slave3-1.sqlite',
109
- 'test-slave3-2.sqlite'
153
+ 'test-replica3-1.sqlite',
154
+ 'test-replica3-2.sqlite'
110
155
  ]
111
156
  end
112
157
  end
113
158
  end
114
-
115
- end
159
+ end
@@ -1,20 +1,23 @@
1
- module Helpers
1
+ # frozen_string_literal: true
2
2
 
3
- def configuration_with_slaves
4
- return YAML.load(<<-end)
5
- adapter: sqlite3
6
- database: spec/test.sqlite
7
- encoding: utf-8
8
- multidb:
9
- databases:
10
- slave1:
11
- database: spec/test-slave1.sqlite
12
- slave2:
13
- database: spec/test-slave2.sqlite
14
- slave3:
15
- - database: spec/test-slave3-1.sqlite
16
- - database: spec/test-slave3-2.sqlite
17
- end
3
+ module Helpers
4
+ def configuration_with_replicas
5
+ YAML.safe_load(<<~YAML)
6
+ adapter: sqlite3
7
+ database: spec/test.sqlite
8
+ encoding: utf-8
9
+ multidb:
10
+ databases:
11
+ replica1:
12
+ database: spec/test-replica1.sqlite
13
+ replica2:
14
+ database: spec/test-replica2.sqlite
15
+ replica3:
16
+ - database: spec/test-replica3-1.sqlite
17
+ - database: spec/test-replica3-2.sqlite
18
+ replica_alias:
19
+ database: spec/test-replica2.sqlite
20
+ alias: replica2
21
+ YAML
18
22
  end
19
-
20
- end
23
+ end
@@ -1,23 +1,26 @@
1
- environment = ENV['RACK_ENV'] ||= 'test'
1
+ # frozen_string_literal: true
2
+
3
+ ENV['RACK_ENV'] ||= 'test'
2
4
 
3
5
  require 'rspec'
4
6
  require 'yaml'
5
7
  require 'active_record'
6
8
  require 'fileutils'
7
9
 
8
- $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
10
+ $LOAD_PATH.unshift(File.expand_path('lib', __dir__))
9
11
  require 'multidb'
10
12
 
11
13
  require_relative 'helpers'
12
14
 
13
15
  RSpec.configure do |config|
14
16
  config.include Helpers
17
+ config.expect_with(:rspec) { |c| c.syntax = :should }
15
18
  config.before :each do
16
19
  ActiveRecord::Base.clear_all_connections!
17
20
  Multidb.reset!
18
21
  end
19
22
  config.after :each do
20
- Dir.glob(File.expand_path('../test*.sqlite', __FILE__)).each do |f|
23
+ Dir.glob(File.expand_path('test*.sqlite', __dir__)).each do |f|
21
24
  FileUtils.rm(f)
22
25
  end
23
26
  end
metadata CHANGED
@@ -1,91 +1,138 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ar-multidb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.12
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Staubo
8
- autorequire:
8
+ - Edward Rudd
9
+ autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2014-03-31 00:00:00.000000000 Z
12
+ date: 2020-07-20 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
- name: activesupport
15
+ name: activerecord
15
16
  requirement: !ruby/object:Gem::Requirement
16
17
  requirements:
17
- - - '>='
18
+ - - ">="
18
19
  - !ruby/object:Gem::Version
19
- version: '3.0'
20
+ version: '5.1'
21
+ - - "<"
22
+ - !ruby/object:Gem::Version
23
+ version: '6.0'
20
24
  type: :runtime
21
25
  prerelease: false
22
26
  version_requirements: !ruby/object:Gem::Requirement
23
27
  requirements:
24
- - - '>='
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ version: '5.1'
31
+ - - "<"
25
32
  - !ruby/object:Gem::Version
26
- version: '3.0'
33
+ version: '6.0'
27
34
  - !ruby/object:Gem::Dependency
28
- name: activerecord
35
+ name: activesupport
29
36
  requirement: !ruby/object:Gem::Requirement
30
37
  requirements:
31
- - - '>='
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '5.1'
41
+ - - "<"
32
42
  - !ruby/object:Gem::Version
33
- version: '3.0'
43
+ version: '6.0'
34
44
  type: :runtime
35
45
  prerelease: false
36
46
  version_requirements: !ruby/object:Gem::Requirement
37
47
  requirements:
38
- - - '>='
48
+ - - ">="
39
49
  - !ruby/object:Gem::Version
40
- version: '3.0'
50
+ version: '5.1'
51
+ - - "<"
52
+ - !ruby/object:Gem::Version
53
+ version: '6.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '12.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '12.0'
41
68
  - !ruby/object:Gem::Dependency
42
69
  name: rspec
43
70
  requirement: !ruby/object:Gem::Requirement
44
71
  requirements:
45
- - - '>='
72
+ - - "~>"
46
73
  - !ruby/object:Gem::Version
47
- version: '0'
74
+ version: '3.8'
48
75
  type: :development
49
76
  prerelease: false
50
77
  version_requirements: !ruby/object:Gem::Requirement
51
78
  requirements:
52
- - - '>='
79
+ - - "~>"
53
80
  - !ruby/object:Gem::Version
54
- version: '0'
81
+ version: '3.8'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rubocop
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: 0.88.0
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: 0.88.0
55
96
  - !ruby/object:Gem::Dependency
56
97
  name: sqlite3
57
98
  requirement: !ruby/object:Gem::Requirement
58
99
  requirements:
59
- - - '>='
100
+ - - "~>"
60
101
  - !ruby/object:Gem::Version
61
- version: '0'
102
+ version: '1.3'
62
103
  type: :development
63
104
  prerelease: false
64
105
  version_requirements: !ruby/object:Gem::Requirement
65
106
  requirements:
66
- - - '>='
107
+ - - "~>"
67
108
  - !ruby/object:Gem::Version
68
- version: '0'
109
+ version: '1.3'
69
110
  description: Multidb is an ActiveRecord extension for switching between multiple database
70
- connections, such as master/slave setups.
111
+ connections, such as primary/replica setups.
71
112
  email:
72
113
  - alex@bengler.no
114
+ - urkle@outoforder.cc
73
115
  executables: []
74
116
  extensions: []
75
117
  extra_rdoc_files: []
76
118
  files:
77
- - .gitignore
78
- - .travis.yml
119
+ - ".gitignore"
120
+ - ".rubocop.yml"
121
+ - ".travis.yml"
122
+ - CHANGELOG.md
79
123
  - Gemfile
80
- - Gemfile.lock
81
124
  - LICENSE
82
125
  - README.markdown
83
126
  - Rakefile
84
127
  - ar-multidb.gemspec
128
+ - gemfiles/activerecord51.gemfile
129
+ - gemfiles/activerecord52.gemfile
85
130
  - lib/ar-multidb.rb
86
131
  - lib/multidb.rb
87
132
  - lib/multidb/balancer.rb
133
+ - lib/multidb/candidate.rb
88
134
  - lib/multidb/configuration.rb
135
+ - lib/multidb/log_subscriber.rb
89
136
  - lib/multidb/model_extensions.rb
90
137
  - lib/multidb/version.rb
91
138
  - spec/balancer_spec.rb
@@ -94,29 +141,27 @@ files:
94
141
  homepage: ''
95
142
  licenses: []
96
143
  metadata: {}
97
- post_install_message:
144
+ post_install_message:
98
145
  rdoc_options: []
99
146
  require_paths:
100
147
  - lib
101
148
  required_ruby_version: !ruby/object:Gem::Requirement
102
149
  requirements:
103
- - - '>='
150
+ - - ">="
104
151
  - !ruby/object:Gem::Version
105
- version: '0'
152
+ version: 2.4.0
106
153
  required_rubygems_version: !ruby/object:Gem::Requirement
107
154
  requirements:
108
- - - '>='
155
+ - - ">="
109
156
  - !ruby/object:Gem::Version
110
157
  version: '0'
111
158
  requirements: []
112
- rubyforge_project: ar-multidb
113
- rubygems_version: 2.0.3
114
- signing_key:
159
+ rubygems_version: 3.0.6
160
+ signing_key:
115
161
  specification_version: 4
116
162
  summary: Multidb is an ActiveRecord extension for switching between multiple database
117
- connections, such as master/slave setups.
163
+ connections, such as primary/replica setups.
118
164
  test_files:
119
165
  - spec/balancer_spec.rb
120
166
  - spec/helpers.rb
121
167
  - spec/spec_helper.rb
122
- has_rdoc:
@@ -1,54 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- ar-multidb (0.1.11)
5
- activerecord (>= 3.0)
6
- activesupport (>= 3.0)
7
-
8
- GEM
9
- remote: http://rubygems.org/
10
- specs:
11
- activemodel (4.0.2)
12
- activesupport (= 4.0.2)
13
- builder (~> 3.1.0)
14
- activerecord (4.0.2)
15
- activemodel (= 4.0.2)
16
- activerecord-deprecated_finders (~> 1.0.2)
17
- activesupport (= 4.0.2)
18
- arel (~> 4.0.0)
19
- activerecord-deprecated_finders (1.0.3)
20
- activesupport (4.0.2)
21
- i18n (~> 0.6, >= 0.6.4)
22
- minitest (~> 4.2)
23
- multi_json (~> 1.3)
24
- thread_safe (~> 0.1)
25
- tzinfo (~> 0.3.37)
26
- arel (4.0.1)
27
- atomic (1.1.14)
28
- builder (3.1.4)
29
- diff-lcs (1.2.5)
30
- i18n (0.6.9)
31
- minitest (4.7.5)
32
- multi_json (1.8.4)
33
- rake (10.1.1)
34
- rspec (2.14.1)
35
- rspec-core (~> 2.14.0)
36
- rspec-expectations (~> 2.14.0)
37
- rspec-mocks (~> 2.14.0)
38
- rspec-core (2.14.7)
39
- rspec-expectations (2.14.5)
40
- diff-lcs (>= 1.1.3, < 2.0)
41
- rspec-mocks (2.14.5)
42
- sqlite3 (1.3.8)
43
- thread_safe (0.1.3)
44
- atomic
45
- tzinfo (0.3.38)
46
-
47
- PLATFORMS
48
- ruby
49
-
50
- DEPENDENCIES
51
- ar-multidb!
52
- rake
53
- rspec
54
- sqlite3