diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 502f427b4..45e9c2f21 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -1,12 +1,7 @@ class ApplicationRecord < ActiveRecord::Base primary_abstract_class - # SQLite doesn't use separate replica databases - if ENV.fetch("DATABASE_ADAPTER", "mysql") == "sqlite" - connects_to database: { writing: :primary, reading: :primary } - else - connects_to database: { writing: :primary, reading: :replica } - end + configure_replica_connections attribute :id, :uuid, default: -> { ActiveRecord::Type::Uuid.generate } end diff --git a/config/initializers/active_storage.rb b/config/initializers/active_storage.rb index 361d86075..d00bd74bf 100644 --- a/config/initializers/active_storage.rb +++ b/config/initializers/active_storage.rb @@ -4,14 +4,8 @@ ActiveSupport.on_load(:active_storage_blob) do end end -# Use DB read/write splitting for Active Storage models ActiveSupport.on_load(:active_storage_record) do - # SQLite doesn't use separate replica databases - if ENV.fetch("DATABASE_ADAPTER", "mysql") == "sqlite" - connects_to database: { writing: :primary, reading: :primary } - else - connects_to database: { writing: :primary, reading: :replica } - end + configure_replica_connections end module ActiveStorageControllerExtensions diff --git a/config/initializers/database_role_logging.rb b/config/initializers/database_role_logging.rb index 698d52080..e2b8779b8 100644 --- a/config/initializers/database_role_logging.rb +++ b/config/initializers/database_role_logging.rb @@ -1,3 +1,5 @@ +require_relative "extensions" + class DatabaseRoleLogger def initialize(app) @app = app @@ -10,4 +12,6 @@ class DatabaseRoleLogger end end -Rails.application.config.middleware.insert_after ActiveRecord::Middleware::DatabaseSelector, DatabaseRoleLogger +if ActiveRecord::Base.replica_configured? + Rails.application.config.middleware.insert_after ActiveRecord::Middleware::DatabaseSelector, DatabaseRoleLogger +end diff --git a/config/initializers/multi_db.rb b/config/initializers/multi_db.rb index b4d91085d..f7b1be6f5 100644 --- a/config/initializers/multi_db.rb +++ b/config/initializers/multi_db.rb @@ -1,7 +1,10 @@ require "deployment" +require_relative "extensions" -Rails.application.configure do - config.active_record.database_selector = { delay: 1.second } - config.active_record.database_resolver = Deployment::DatabaseResolver - config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session +if ActiveRecord::Base.replica_configured? + Rails.application.configure do + config.active_record.database_selector = { delay: 1.second } + config.active_record.database_resolver = Deployment::DatabaseResolver + config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session + end end diff --git a/config/initializers/tenanting/account_slug.rb b/config/initializers/tenanting/account_slug.rb index dffbb6bf8..9d6d83ca7 100644 --- a/config/initializers/tenanting/account_slug.rb +++ b/config/initializers/tenanting/account_slug.rb @@ -43,6 +43,4 @@ module AccountSlug def self.encode(id) FORMAT % id end end -Rails.application.config.middleware.tap do |stack| - stack.insert_before ActiveRecord::Middleware::DatabaseSelector, AccountSlug::Extractor -end +Rails.application.config.middleware.insert_after Rack::TempfileReaper, AccountSlug::Extractor diff --git a/lib/rails_ext/active_record_replica_support.rb b/lib/rails_ext/active_record_replica_support.rb new file mode 100644 index 000000000..2625a1ac9 --- /dev/null +++ b/lib/rails_ext/active_record_replica_support.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +# Adds a helper method to check if replica database connections are configured +# and automatically configures read/write splitting when replicas are available. +# +# Usage: +# class ApplicationRecord < ActiveRecord::Base +# configure_replica_connections +# end +module ActiveRecordReplicaSupport + extend ActiveSupport::Concern + + class_methods do + # Automatically configures connects_to for read/write splitting if a replica + # database is configured for the current environment. This is a no-op if no + # replica configuration exists. + # + # Example: + # class ApplicationRecord < ActiveRecord::Base + # configure_replica_connections + # end + def configure_replica_connections + if replica_configured? + connects_to database: { writing: :primary, reading: :replica } + end + end + + # Returns true if a replica database configuration exists for the current + # environment. This allows different database adapters to opt in or out of + # read/write splitting based on their database.yml configuration. + # + # Example: + # ApplicationRecord.replica_configured? # => true for MySQL, false for SQLite + def replica_configured? + configurations.find_db_config("replica").present? + end + end +end + +ActiveRecord::Base.include ActiveRecordReplicaSupport