Created
May 22, 2023 14:41
-
-
Save MadBomber/620d8c10f0e6b6a9ad449774c4ee64ee to your computer and use it in GitHub Desktop.
Auto-connection switching between read and write database
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# app/models/application_record.rb | |
class ApplicationRecord < ActiveRecord::Base | |
self.abstract_class = true | |
# When true, the database automatically switches connections | |
@@auto_switch_connection = true | |
# returns the name of the read replica if one is defined in database.yml, otherwise returns :primary | |
def self.reading_db | |
db_configs = ApplicationRecord.configurations.configs_for(env_name: Rails.env, include_replicas: true) | |
db_configs.find(&:replica?)&.name&.to_sym || :primary | |
end | |
connects_to database: { | |
writing: :primary, | |
reading: ApplicationRecord.reading_db | |
} | |
before_save :switch_to_primary_database | |
before_destroy :switch_to_primary_database | |
after_commit :switch_to_read_replica | |
after_rollback :switch_to_read_replica | |
# These cannot be private if we want to call them | |
# from inside a sidekiq worker or rails console. | |
# private | |
def switch_to_primary_database(switch_connection=nil) | |
ApplicationRecord.switch_to_primary_database(switch_connection) | |
end | |
def self.switch_to_primary_database(switch_connection=nil) | |
unless switch_connection.nil? | |
self.auto_switch_connection = switch_connection | |
ApplicationRecord.switch_to :writing | |
return | |
end | |
ApplicationRecord.switch_to(:writing) if ApplicationRecord.auto_switch_connection? | |
end | |
def switch_to_read_replica(switch_connection=nil) | |
ApplicationRecord.switch_to_read_replica(switch_connection) | |
end | |
def self.switch_to_read_replica(switch_connection=nil) | |
unless switch_connection.nil? | |
self.auto_switch_connection = switch_connection | |
ApplicationRecord.switch_to :reading | |
return | |
end | |
ApplicationRecord.switch_to(:reading) if ApplicationRecord.auto_switch_connection? | |
end | |
def self.switch_to(role) | |
unless [:reading, :writing].include? role | |
raise BadParameterError, "database rple is incorrect: #{role}" | |
end | |
ActiveRecord::Base.default_role = role | |
end | |
def self.auto_switch_connection? | |
!!@@auto_switch_connection | |
end | |
def self.auto_switch_connection=(a_boolean) | |
@@auto_switch_connection = a_boolean | |
end | |
def self.db_role | |
ActiveRecord::Base.default_role | |
end | |
def self.db_name | |
ActiveRecord::Base.connection.current_database | |
end | |
end |
When implementing this and adding the read only line in my application:
config.active_record.default_role = :reading
I am unable to run migrations. Any idea how to get migrations to run with a writer role?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I feel like I'm treating the connection as if there is only one and not a pool of concurrent connections. For example the
auto_switch_connection
is defined as a class variable. As such it impacts all sub-classes of the ApplicationRecord.The cakk backs are really instance related. Those instances being of sub-classes of ApplicationRecord.
So maybe `auto_switch_connection should be an instance variable (or a class variable on the sub-class) so that querys to one table can be on the reading role while queries to another table could be on the writing role.