rails_failover 1.0.0 → 2.0.1

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
  SHA256:
3
- metadata.gz: 51e57db306d29440ba74abe00ee001dc4be39121c552f3f7797cb7d0c5bfbba0
4
- data.tar.gz: 10dc1a892042c6432ccd196905d744d09235abe3887cc039c46a75a1eafebcb6
3
+ metadata.gz: 22d00b2fbfa1a5c8fc28cae9244e93700885a41da8f1a824bf0c381d4606fafa
4
+ data.tar.gz: 6e9763206550dfa0101ca5680f740aae32ec9d8d452b469dc7d14c033848c56c
5
5
  SHA512:
6
- metadata.gz: 8043b0080a388a26d90c06c0887455ff1e1630892beafea4171243e9aa17552b83db7d2523e95da6787c1178f883574e32b43dcc8f290e1aa1dbb0ffef153271
7
- data.tar.gz: 2b604f3a8c3c4a5e2d3e33098ead78a0c426c3be6e00f9560c2753e277369f46f25523dcc20e59be664af7c59d3a82f8979f3e05bb8bb034be06f33393341835
6
+ metadata.gz: 4e9fafd722150cda45594b10ff642b4296c29e14bd604a8c0c8cd1fbc89706bbcd3b339c5043478056d32302c13d59b510fb77eaa0c248eb363527cf90d74715
7
+ data.tar.gz: 55dd5bde9d208dbf9103a73474af639ef03447e4aba5c6012937670b6f627cc5119bcd09cc8559ec6ded4b7fc895e2190d55662acf5b79e5592e8f548fb4e32d
@@ -35,7 +35,7 @@ jobs:
35
35
  strategy:
36
36
  fail-fast: false
37
37
  matrix:
38
- ruby: ['2.7', '3.0', '3.1', '3.2']
38
+ ruby: ['3.0', '3.1', '3.2']
39
39
 
40
40
  steps:
41
41
  - uses: actions/checkout@v3
@@ -59,13 +59,13 @@ jobs:
59
59
  strategy:
60
60
  fail-fast: false
61
61
  matrix:
62
- ruby: ['3.2', '3.1', '3.0', '2.7']
62
+ ruby: ['3.2', '3.1', '3.0']
63
63
  rails: ['7.0.0']
64
64
  include:
65
65
  - ruby: '3.2'
66
66
  rails: '6.1.0'
67
67
  - ruby: '3.2'
68
- rails: '6.0.0'
68
+ rails: 'edge'
69
69
 
70
70
  steps:
71
71
  - uses: actions/checkout@v3
@@ -97,7 +97,7 @@ jobs:
97
97
  - uses: actions/checkout@v3
98
98
 
99
99
  - name: Release Gem
100
- uses: discourse/publish-rubygems-action@v2
100
+ uses: discourse/publish-rubygems-action@v3
101
101
  env:
102
102
  RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
103
103
  GIT_EMAIL: team@discourse.org
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --color
2
2
  --require spec_helper
3
+ --format documentation
data/CHANGELOG.md CHANGED
@@ -5,7 +5,13 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [2.0.1] - 2023-05-30
9
+
10
+ - FIX: Use `next` instead of `break` to avoid a local jump error
11
+
12
+ ## [2.0.0] - 2023-05-16
13
+
14
+ - DEV: Compatibility with Rails 7.1+ (drop support for Rails 6.0 & ruby 2.7)
9
15
 
10
16
  ## [1.0.0] - 2023-04-07
11
17
 
data/README.md CHANGED
@@ -37,7 +37,8 @@ production:
37
37
  replica_port: <replica db server port>
38
38
  ```
39
39
 
40
- The gem will automatically create an `ActiveRecord::ConnectionAdapters::ConnectionHandler` with the `ActiveRecord.reading_role` as the `handler_key`.
40
+ The gem will automatically create a role (using `ActiveRecord.reading_role`) on
41
+ the default `ActiveRecord` connection handler.
41
42
 
42
43
  #### Failover/Fallback Hooks
43
44
 
@@ -53,8 +54,6 @@ end
53
54
 
54
55
  #### Multiple connection handlers
55
56
 
56
- Note: This API is unstable and is likely to change when Rails 6.1 is released with sharding support.
57
-
58
57
  ```yml
59
58
  # config/database.yml
60
59
 
@@ -72,7 +71,7 @@ production:
72
71
 
73
72
  # In your ActiveRecord base model or model.
74
73
 
75
- connects_to database: { writing: :primary, second_database_writing: :second_database_writing
74
+ connects_to database: { writing: :primary, second_database_writing: :second_database_writing }
76
75
  ```
77
76
 
78
77
  ### Redis
@@ -55,21 +55,15 @@ module RailsFailover
55
55
  active_handler_keys = []
56
56
 
57
57
  primaries_down.keys.each do |handler_key|
58
- connection_handler = ::ActiveRecord::Base.connection_handlers[handler_key]
59
-
60
- connection_pool = connection_handler.retrieve_connection_pool(spec_name)
61
- if connection_pool.respond_to?(:db_config)
62
- config = connection_pool.db_config.configuration_hash
63
- adapter_method = connection_pool.db_config.adapter_method
64
- else
65
- config = connection_pool.spec.config
66
- adapter_method = connection_pool.spec.adapter_method
67
- end
68
58
  logger.debug "#{Process.pid} Checking server for '#{handler_key} #{spec_name}'..."
69
59
  connection_active = false
70
60
 
71
61
  begin
72
- connection = ::ActiveRecord::Base.public_send(adapter_method, config)
62
+ connection =
63
+ ::ActiveRecord::Base.connection_handler.retrieve_connection(
64
+ spec_name,
65
+ role: handler_key,
66
+ )
73
67
  connection_active = connection.active?
74
68
  rescue => e
75
69
  logger.debug "#{Process.pid} Connection to server for '#{handler_key} #{spec_name}' failed with '#{e.message}'"
@@ -49,9 +49,8 @@ module RailsFailover
49
49
  writing_role = resolve_writing_role(current_role, is_writing_role)
50
50
 
51
51
  role =
52
- if primary_down =
53
- self.class.force_reading_role_callback&.call(env) ||
54
- Handler.instance.primary_down?(writing_role)
52
+ if self.class.force_reading_role_callback&.call(env) ||
53
+ Handler.instance.primary_down?(writing_role)
55
54
  reading_role = resolve_reading_role(current_role, is_writing_role)
56
55
  ensure_reading_connection_established!(
57
56
  writing_role: writing_role,
@@ -75,48 +74,39 @@ module RailsFailover
75
74
  private
76
75
 
77
76
  def ensure_reading_connection_established!(writing_role:, reading_role:)
78
- ::ActiveRecord::Base.connection_handlers[reading_role] ||= begin
79
- handler = ::ActiveRecord::ConnectionAdapters::ConnectionHandler.new
80
-
81
- ::ActiveRecord::Base.connection_handlers[writing_role].connection_pools.each do |pool|
82
- if pool.respond_to?(:db_config)
83
- config = pool.db_config.configuration_hash
84
- else
85
- config = pool.spec.config
86
- end
87
- ::RailsFailover::ActiveRecord.establish_reading_connection(handler, config)
77
+ connection_handler = ::ActiveRecord::Base.connection_handler
78
+ connection_handler
79
+ .connection_pools(writing_role)
80
+ .each do |connection_pool|
81
+ config = connection_pool.db_config.configuration_hash
82
+ RailsFailover::ActiveRecord.establish_reading_connection(
83
+ connection_handler,
84
+ config,
85
+ role: reading_role,
86
+ )
88
87
  end
89
-
90
- handler
91
- end
92
88
  end
93
89
 
94
90
  def resolve_writing_role(current_role, is_writing_role)
95
- if is_writing_role
96
- current_role
97
- else
98
- current_role
99
- .to_s
100
- .sub(
101
- /#{RailsFailover::ActiveRecord.reading_role}$/,
102
- RailsFailover::ActiveRecord.writing_role.to_s,
103
- )
104
- .to_sym
105
- end
91
+ return current_role if is_writing_role
92
+ current_role
93
+ .to_s
94
+ .sub(
95
+ /#{RailsFailover::ActiveRecord.reading_role}$/,
96
+ RailsFailover::ActiveRecord.writing_role.to_s,
97
+ )
98
+ .to_sym
106
99
  end
107
100
 
108
101
  def resolve_reading_role(current_role, is_writing_role)
109
- if is_writing_role
110
- current_role
111
- .to_s
112
- .sub(
113
- /#{RailsFailover::ActiveRecord.writing_role}$/,
114
- RailsFailover::ActiveRecord.reading_role.to_s,
115
- )
116
- .to_sym
117
- else
118
- current_role
119
- end
102
+ return current_role unless is_writing_role
103
+ current_role
104
+ .to_s
105
+ .sub(
106
+ /#{RailsFailover::ActiveRecord.writing_role}$/,
107
+ RailsFailover::ActiveRecord.reading_role.to_s,
108
+ )
109
+ .to_sym
120
110
  end
121
111
  end
122
112
  end
@@ -4,70 +4,36 @@ module RailsFailover
4
4
  module ActiveRecord
5
5
  class Railtie < ::Rails::Railtie
6
6
  initializer "rails_failover.init", after: "active_record.initialize_database" do |app|
7
- # AR 6.0 / 6.1 compat
8
- config =
9
- if ::ActiveRecord::Base.respond_to? :connection_db_config
10
- ::ActiveRecord::Base.connection_db_config.configuration_hash
11
- else
12
- ::ActiveRecord::Base.connection_config
13
- end
14
-
15
7
  app.config.active_record_rails_failover = false
16
-
17
- if !!(config[:replica_host] && config[:replica_port])
18
- app.config.active_record_rails_failover = true
19
-
20
- ::ActiveSupport.on_load(:active_record) do
21
- Handler.instance
22
-
23
- # We are doing this manually for now since we're awaiting Rails 6.1 to be released which will
24
- # have more stable ActiveRecord APIs for handling multiple databases with different roles.
25
- ::ActiveRecord::Base.connection_handlers[
26
- RailsFailover::ActiveRecord.reading_role
27
- ] = ::ActiveRecord::ConnectionAdapters::ConnectionHandler.new
28
-
29
- ::ActiveRecord::Base.connection_handlers[RailsFailover::ActiveRecord.writing_role]
30
- .connection_pools
31
- .each do |connection_pool|
32
- if connection_pool.respond_to?(:db_config)
33
- config = connection_pool.db_config.configuration_hash
34
- else
35
- config = connection_pool.spec.config
36
- end
37
- RailsFailover::ActiveRecord.establish_reading_connection(
38
- ::ActiveRecord::Base.connection_handlers[RailsFailover::ActiveRecord.reading_role],
39
- config,
40
- )
41
- end
42
-
43
- begin
44
- ::ActiveRecord::Base.connection
45
- rescue ::ActiveRecord::NoDatabaseError
46
- # Do nothing since database hasn't been created
47
- rescue ::PG::Error, ::ActiveRecord::ConnectionNotEstablished
48
- Handler.instance.verify_primary(RailsFailover::ActiveRecord.writing_role)
49
- ::ActiveRecord::Base.connection_handler =
50
- ::ActiveRecord::Base.lookup_connection_handler(:reading)
51
- end
8
+ config = RailsFailover::ActiveRecord.config
9
+ next unless config[:replica_host] && config[:replica_port]
10
+
11
+ app.config.active_record_rails_failover = true
12
+ ::ActiveSupport.on_load(:active_record) do
13
+ begin
14
+ ::ActiveRecord::Base.connection
15
+ rescue ::ActiveRecord::NoDatabaseError
16
+ # Do nothing since database hasn't been created
17
+ rescue ::PG::Error, ::ActiveRecord::ConnectionNotEstablished
18
+ Handler.instance.verify_primary(RailsFailover::ActiveRecord.writing_role)
52
19
  end
53
20
  end
54
21
  end
55
22
 
56
23
  initializer "rails_failover.insert_middleware" do |app|
57
- if app.config.active_record_rails_failover
58
- ActionDispatch::DebugExceptions.register_interceptor do |request, exception|
59
- RailsFailover::ActiveRecord::Interceptor.handle(request, exception)
60
- end
24
+ next unless app.config.active_record_rails_failover
61
25
 
62
- if !skip_middleware?(app.config)
63
- app.middleware.unshift(::RailsFailover::ActiveRecord::Middleware)
64
- end
26
+ ActionDispatch::DebugExceptions.register_interceptor do |request, exception|
27
+ RailsFailover::ActiveRecord::Interceptor.handle(request, exception)
28
+ end
29
+
30
+ if !skip_middleware?(app.config)
31
+ app.middleware.unshift(RailsFailover::ActiveRecord::Middleware)
65
32
  end
66
33
  end
67
34
 
68
35
  def skip_middleware?(config)
69
- return false if !config.respond_to?(:skip_rails_failover_active_record_middleware)
70
- config.skip_rails_failover_active_record_middleware
36
+ config.try(:skip_rails_failover_active_record_middleware)
71
37
  end
72
38
  end
73
39
  end
@@ -7,77 +7,73 @@ require_relative "active_record/railtie" if defined?(::Rails)
7
7
  require_relative "active_record/middleware"
8
8
  require_relative "active_record/handler"
9
9
 
10
- AR =
11
- (
12
- if ::ActiveRecord.respond_to?(:reading_role)
13
- ::ActiveRecord
14
- else
15
- ::ActiveRecord::Base
16
- end
17
- )
18
-
19
10
  module RailsFailover
20
11
  module ActiveRecord
21
- def self.logger=(logger)
22
- @logger = logger
23
- end
12
+ class << self
13
+ def config
14
+ ::ActiveRecord::Base.connection_db_config.configuration_hash
15
+ end
24
16
 
25
- def self.logger
26
- @logger || Rails.logger
27
- end
17
+ def logger=(logger)
18
+ @logger = logger
19
+ end
28
20
 
29
- def self.verify_primary_frequency_seconds=(seconds)
30
- @verify_primary_frequency_seconds = seconds
31
- end
21
+ def logger
22
+ @logger || Rails.logger
23
+ end
32
24
 
33
- def self.verify_primary_frequency_seconds
34
- @verify_primary_frequency_seconds || 5
35
- end
25
+ def verify_primary_frequency_seconds=(seconds)
26
+ @verify_primary_frequency_seconds = seconds
27
+ end
36
28
 
37
- def self.establish_reading_connection(handler, config)
38
- if config[:replica_host] && config[:replica_port]
29
+ def verify_primary_frequency_seconds
30
+ @verify_primary_frequency_seconds || 5
31
+ end
32
+
33
+ def establish_reading_connection(handler, config, role: reading_role)
34
+ return unless config[:replica_host] && config[:replica_port]
39
35
  replica_config = config.dup
40
36
  replica_config[:host] = replica_config.delete(:replica_host)
41
37
  replica_config[:port] = replica_config.delete(:replica_port)
42
38
  replica_config[:replica] = true
43
- handler.establish_connection(replica_config)
39
+ handler.establish_connection(replica_config, role: role)
44
40
  end
45
- end
46
41
 
47
- def self.register_force_reading_role_callback(&block)
48
- Middleware.force_reading_role_callback = block
49
- end
42
+ def register_force_reading_role_callback(&block)
43
+ Middleware.force_reading_role_callback = block
44
+ end
50
45
 
51
- def self.on_failover(&block)
52
- @on_failover_callback = block
53
- end
46
+ def on_failover(&block)
47
+ @on_failover_callback = block
48
+ end
54
49
 
55
- def self.on_failover_callback!(key)
56
- @on_failover_callback&.call(key)
57
- rescue => e
58
- logger.warn(
59
- "RailsFailover::ActiveRecord.on_failover failed: #{e.class} #{e.message}\n#{e.backtrace.join("\n")}",
60
- )
61
- end
50
+ def on_failover_callback!(key)
51
+ @on_failover_callback&.call(key)
52
+ rescue => e
53
+ logger.warn(
54
+ "RailsFailover::ActiveRecord.on_failover failed: #{e.class} #{e.message}\n#{e.backtrace.join("\n")}",
55
+ )
56
+ end
62
57
 
63
- def self.on_fallback(&block)
64
- @on_fallback_callback = block
65
- end
58
+ def on_fallback(&block)
59
+ @on_fallback_callback = block
60
+ end
66
61
 
67
- def self.on_fallback_callback!(key)
68
- @on_fallback_callback&.call(key)
69
- rescue => e
70
- logger.warn(
71
- "RailsFailover::ActiveRecord.on_fallback failed: #{e.class} #{e.message}\n#{e.backtrace.join("\n")}",
72
- )
73
- end
62
+ def on_fallback_callback!(key)
63
+ @on_fallback_callback&.call(key)
64
+ rescue => e
65
+ logger.warn(
66
+ "RailsFailover::ActiveRecord.on_fallback failed: #{e.class} #{e.message}\n#{e.backtrace.join("\n")}",
67
+ )
68
+ end
74
69
 
75
- def self.reading_role
76
- AR.reading_role
77
- end
70
+ def reading_role
71
+ ::ActiveRecord.try(:reading_role) || ::ActiveRecord::Base.reading_role
72
+ end
78
73
 
79
- def self.writing_role
80
- AR.writing_role
74
+ def writing_role
75
+ ::ActiveRecord.try(:writing_role) || ::ActiveRecord::Base.writing_role
76
+ end
81
77
  end
82
78
  end
83
79
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsFailover
4
- VERSION = "1.0.0"
4
+ VERSION = "2.0.1"
5
5
  end
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.summary = "Failover for ActiveRecord and Redis"
12
12
  spec.homepage = "https://github.com/discourse/rails_failover"
13
13
  spec.license = "MIT"
14
- spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
14
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
15
15
 
16
16
  # Specify which files should be added to the gem when it is released.
17
17
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -23,8 +23,8 @@ Gem::Specification.new do |spec|
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  spec.require_paths = ["lib"]
25
25
 
26
- spec.add_dependency "activerecord", "> 6.0", "< 7.1"
27
- spec.add_dependency "railties", "> 6.0", "< 7.1"
26
+ spec.add_dependency "activerecord", ">= 6.1", "<= 7.1"
27
+ spec.add_dependency "railties", ">= 6.1", "<= 7.1"
28
28
  spec.add_dependency "concurrent-ruby"
29
29
 
30
30
  spec.add_development_dependency "rake", "~> 12.0"
metadata CHANGED
@@ -1,53 +1,53 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_failover
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alan Tan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-04-08 00:00:00.000000000 Z
11
+ date: 2023-05-30 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: '6.0'
20
- - - "<"
19
+ version: '6.1'
20
+ - - "<="
21
21
  - !ruby/object:Gem::Version
22
22
  version: '7.1'
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: '6.0'
30
- - - "<"
29
+ version: '6.1'
30
+ - - "<="
31
31
  - !ruby/object:Gem::Version
32
32
  version: '7.1'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: railties
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - ">"
37
+ - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: '6.0'
40
- - - "<"
39
+ version: '6.1'
40
+ - - "<="
41
41
  - !ruby/object:Gem::Version
42
42
  version: '7.1'
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
46
46
  requirements:
47
- - - ">"
47
+ - - ">="
48
48
  - !ruby/object:Gem::Version
49
- version: '6.0'
50
- - - "<"
49
+ version: '6.1'
50
+ - - "<="
51
51
  - !ruby/object:Gem::Version
52
52
  version: '7.1'
53
53
  - !ruby/object:Gem::Dependency
@@ -237,14 +237,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
237
237
  requirements:
238
238
  - - ">="
239
239
  - !ruby/object:Gem::Version
240
- version: 2.7.0
240
+ version: 3.0.0
241
241
  required_rubygems_version: !ruby/object:Gem::Requirement
242
242
  requirements:
243
243
  - - ">="
244
244
  - !ruby/object:Gem::Version
245
245
  version: '0'
246
246
  requirements: []
247
- rubygems_version: 3.1.6
247
+ rubygems_version: 3.4.10
248
248
  signing_key:
249
249
  specification_version: 4
250
250
  summary: Failover for ActiveRecord and Redis