diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 0a6375be0d..ba933cf09b 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,7 @@ +* Move database and shard selection config options to a generator. + Rather than generating the config options in the production.rb when applications are created, applications can now run a generator to create an initializer and uncomment / update options as needed. All multi-db configuration can be imlpemented in this initializer. + + *Eileen M. Uchitelle* Please check [7-0-stable](https://github.com/rails/rails/blob/7-0-stable/activerecord/CHANGELOG.md) for previous changes. diff --git a/activerecord/lib/rails/generators/active_record/multi_db/multi_db_generator.rb b/activerecord/lib/rails/generators/active_record/multi_db/multi_db_generator.rb new file mode 100644 index 0000000000..32a11b4d9c --- /dev/null +++ b/activerecord/lib/rails/generators/active_record/multi_db/multi_db_generator.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "rails/generators/active_record" + +module ActiveRecord + module Generators # :nodoc: + class MultiDbGenerator < ::Rails::Generators::Base # :nodoc: + source_root File.expand_path("templates", __dir__) + + def create_multi_db + filename = "multi_db.rb" + template filename, "config/initializers/#{filename}" + end + end + end +end diff --git a/activerecord/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt b/activerecord/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt new file mode 100644 index 0000000000..6016d955b7 --- /dev/null +++ b/activerecord/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt @@ -0,0 +1,44 @@ +# Multi-db Configuration +# +# This file is used for configuration settings related to multiple databases. +# +# Enable Database Selector +# +# Inserts middleware to perform automatic connection switching. +# The `database_selector` hash is used to pass options to the DatabaseSelector +# middleware. The `delay` is used to determine how long to wait after a write +# to send a subsequent read to the primary. +# +# The `database_resolver` class is used by the middleware to determine which +# database is appropriate to use based on the time delay. +# +# The `database_resolver_context` class is used by the middleware to set +# timestamps for the last write to the primary. The resolver uses the context +# class timestamps to determine how long to wait before reading from the +# replica. +# +# By default Rails will store a last write timestamp in the session. The +# DatabaseSelector middleware is designed as such you can define your own +# strategy for connection switching and pass that into the middleware through +# these configuration options. +# +# Rails.application.configure do +# config.active_record.database_selector = { delay: 2.seconds } +# config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver +# config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session +# end +# +# Enable Shard Selector +# +# Inserts middleware to perform automatic shard swapping. The `shard_selector` hash +# can be used to pass options to the `ShardSelector` middleware. The `lock` option is +# used to determine whether shard swapping should be prohibited for the request. +# +# The `shard_resolver` option is used by the middleware to determine which shard +# to switch to. The application must provide a mechanism for finding the shard name +# in a proc. See guides for an example. +# +# Rails.application.configure do +# config.active_record.shard_selector = { lock: true } +# config.active_record.shard_resolver = ->(request) { Tenant.find_by!(host: request.host).shard } +# end diff --git a/guides/source/active_record_multiple_databases.md b/guides/source/active_record_multiple_databases.md index a59cd171d1..b03530a4ee 100644 --- a/guides/source/active_record_multiple_databases.md +++ b/guides/source/active_record_multiple_databases.md @@ -288,13 +288,21 @@ automatically write to the writer database. For the specified time after the wri application will read from the primary. For a GET or HEAD request the application will read from the replica unless there was a recent write. -To activate the automatic connection switching middleware, add or uncomment the following -lines in your application config. +To activate the automatic connection switching middleware you can run the automatic swapping +generator: + +``` +$ bin/rails g active_record:multi_db +``` + +And then uncomment the following lines: ```ruby -config.active_record.database_selector = { delay: 2.seconds } -config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver -config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session +Rails.application.configure do + config.active_record.database_selector = { delay: 2.seconds } + config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver + config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session +end ``` Rails guarantees "read your own write" and will send your GET or HEAD request to the @@ -442,13 +450,23 @@ inside the block. If `lock` is false, then shard swapping will be allowed. For tenant based sharding, `lock` should always be true to prevent application code from mistakenly switching between tenants. -Options can be set in the config: +The same generator as the database selector can be used to generate the file for +automatic shard swapping: -```ruby -config.active_record.shard_selector = { lock: true } +``` +$ bin/rails g active_record:multi_db ``` -Applications must also provide the code for the resolver as it depends on application +Then in the file uncomment the following: + +```ruby +Rails.application.configure do + config.active_record.shard_selector = { lock: true } + config.active_record.shard_resolver = ->(request) { Tenant.find_by!(host: request.host).shard } +end +``` + +Applications must provide the code for the resolver as it depends on application specific models. An example resolver would look like this: ```ruby diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index 96a67d174f..0fb7d0976e 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -104,35 +104,4 @@ Rails.application.configure do # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false <%- end -%> - - # Inserts middleware to perform automatic connection switching. - # The `database_selector` hash is used to pass options to the DatabaseSelector - # middleware. The `delay` is used to determine how long to wait after a write - # to send a subsequent read to the primary. - # - # The `database_resolver` class is used by the middleware to determine which - # database is appropriate to use based on the time delay. - # - # The `database_resolver_context` class is used by the middleware to set - # timestamps for the last write to the primary. The resolver uses the context - # class timestamps to determine how long to wait before reading from the - # replica. - # - # By default Rails will store a last write timestamp in the session. The - # DatabaseSelector middleware is designed as such you can define your own - # strategy for connection switching and pass that into the middleware through - # these configuration options. - # config.active_record.database_selector = { delay: 2.seconds } - # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver - # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session - - # Inserts middleware to perform automatic shard swapping. The `shard_selector` hash - # can be used to pass options to the `ShardSelector` middleware. The `lock` option is - # used to determine whether shard swapping should be prohibited for the request. - # - # The `shard_resolver` option is used by the middleware to determine which shard - # to switch to. The application must provide a mechanism for finding the shard name - # in a proc. See guides for an example. - # config.active_record.shard_selector = { lock: true } - # config.active_record.shard_resolver = ->(request) { Tenant.find_by!(host: request.host).shard } end diff --git a/railties/test/generators/multi_db_generator_test.rb b/railties/test/generators/multi_db_generator_test.rb new file mode 100644 index 0000000000..4b279b9b4f --- /dev/null +++ b/railties/test/generators/multi_db_generator_test.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require "generators/generators_test_helper" +require "rails/generators/active_record/multi_db/multi_db_generator" + +class MultiDbGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + tests ActiveRecord::Generators::MultiDbGenerator + + def test_multi_db_skeleton_is_created + run_generator + assert_file "config/initializers/multi_db.rb" do |record| + assert_match(/Multi-db Configuration/, record) + assert_match(/config.active_record.database_resolver/, record) + assert_match(/config.active_record.shard_resolver/, record) + end + end +end