rails_failover 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 51e57db306d29440ba74abe00ee001dc4be39121c552f3f7797cb7d0c5bfbba0
4
- data.tar.gz: 10dc1a892042c6432ccd196905d744d09235abe3887cc039c46a75a1eafebcb6
3
+ metadata.gz: b769d81f278cffd6d960f9a43da2feb15d46e23dd35d2608dbef0fdb04688a70
4
+ data.tar.gz: a72536269e00455cf0506f68dfbbdf6c98a7b7c8a24064532a7219fa31b4bec6
5
5
  SHA512:
6
- metadata.gz: 8043b0080a388a26d90c06c0887455ff1e1630892beafea4171243e9aa17552b83db7d2523e95da6787c1178f883574e32b43dcc8f290e1aa1dbb0ffef153271
7
- data.tar.gz: 2b604f3a8c3c4a5e2d3e33098ead78a0c426c3be6e00f9560c2753e277369f46f25523dcc20e59be664af7c59d3a82f8979f3e05bb8bb034be06f33393341835
6
+ metadata.gz: 33f8835985676d80ba1701eda774322db0701040691e3164218bbf931901314a277ad63c926634f7ac6270f43e5899a7a90030b03f7ec53badb618bac0a187b3
7
+ data.tar.gz: a219d8441c37438bc1ea367fc99920cf3090728a5be482dd59a68f80a5690effa3f2a2a059085675980ed2e816e662ca3c485b12543437bde2801e07d4adfa6b
@@ -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
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.0.0] - 2023-05-16
11
+
12
+ - DEV: Compatibility with Rails 7.1+ (drop support for Rails 6.0 & ruby 2.7)
13
+
10
14
  ## [1.0.0] - 2023-04-07
11
15
 
12
16
  - DEV: Remove the support for Ruby < 2.7
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,51 +4,18 @@ 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
+ break 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
@@ -60,14 +27,13 @@ module RailsFailover
60
27
  end
61
28
 
62
29
  if !skip_middleware?(app.config)
63
- app.middleware.unshift(::RailsFailover::ActiveRecord::Middleware)
30
+ app.middleware.unshift(RailsFailover::ActiveRecord::Middleware)
64
31
  end
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.0"
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.0
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