diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 2f671cdfca..77b94e098d 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,9 @@ +* Ensure the default configuration is considered primary or first for an environment + + If a multiple database application provides a configuration named primary, that will be treated as default. In applications that do not have a primary entry, the default database configuration will be the first configuration for an environment. + + *Eileen M. Uchitelle* + * Allow `where` references association names as joined table name aliases. ```ruby diff --git a/activerecord/lib/active_record/database_configurations.rb b/activerecord/lib/active_record/database_configurations.rb index 0c4f478876..5fb60758d4 100644 --- a/activerecord/lib/active_record/database_configurations.rb +++ b/activerecord/lib/active_record/database_configurations.rb @@ -87,6 +87,20 @@ module ActiveRecord end end + # A primary configuration is one that is named primary or if there is + # no primary, the first configuration for an environment will be treated + # as primary. This is used as the "default" configuration and is used + # when the application needs to treat one configuration differently. For + # example, when Rails dumps the schema, the primary configuration's schema + # file will be named `schema.rb` instead of `primary_schema.rb`. + def primary?(name) # :nodoc: + return true if name == "primary" + + first_config = find_db_config(default_env) + first_config && name == first_config.name + end + + # Returns the DatabaseConfigurations object as a Hash. def to_h configurations.inject({}) do |memo, db_config| diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 46656373fd..14e55aa2b7 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -407,21 +407,21 @@ module ActiveRecord end end - def dump_filename(name, format = ActiveRecord::Base.schema_format) - filename = if name == "primary" + def dump_filename(db_config_name, format = ActiveRecord::Base.schema_format) + filename = if ActiveRecord::Base.configurations.primary?(db_config_name) schema_file_type(format) else - "#{name}_#{schema_file_type(format)}" + "#{db_config_name}_#{schema_file_type(format)}" end ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename) end - def cache_dump_filename(name, schema_cache_path: nil) - filename = if name == "primary" + def cache_dump_filename(db_config_name, schema_cache_path: nil) + filename = if ActiveRecord::Base.configurations.primary?(db_config_name) "schema_cache.yml" else - "#{name}_schema_cache.yml" + "#{db_config_name}_schema_cache.yml" end schema_cache_path || ENV["SCHEMA_CACHE"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename) diff --git a/guides/source/active_record_multiple_databases.md b/guides/source/active_record_multiple_databases.md index 7813820274..03d0c49518 100644 --- a/guides/source/active_record_multiple_databases.md +++ b/guides/source/active_record_multiple_databases.md @@ -21,9 +21,9 @@ so you don't have to store your data all in one place. At this time the following features are supported: -* Multiple primary databases and a replica for each +* Multiple writer databases and a replica for each * Automatic connection switching for the model you're working with -* Automatic swapping between the primary and replica depending on the HTTP verb +* Automatic swapping between the writer and replica depending on the HTTP verb and recent writes * Rails tasks for creating, dropping, migrating, and interacting with the multiple databases @@ -40,7 +40,7 @@ The following features are not (yet) supported: While Rails tries to do most of the work for you there are still some steps you'll need to do to get your application ready for multiple databases. -Let's say we have an application with a single primary database and we need to add a +Let's say we have an application with a single writer database and we need to add a new database for some new tables we're adding. The name of the new database will be "animals". @@ -53,8 +53,15 @@ production: adapter: mysql ``` -Let's add a replica for the primary, a new writer called animals and a replica for that -as well. To do this we need to change our `database.yml` from a 2-tier to a 3-tier config. +Let's add a replica for the first configuration, and a second database called animals and a +replica for that as well. To do this we need to change our `database.yml` from a 2-tier +to a 3-tier config. + +If a primary configuration is provided this will be used as the "default" configuration. If +there is no configuration named "primary" Rails will use the first configuration for an +environment. The default configurations will use the default Rails filenames. For example +primary configurations will use `schema.rb` for the schema file whereas all other entries +will use `[CONFIGURATION_NAMESPACE]_schema.rb` for the filename. ```yaml production: @@ -81,15 +88,17 @@ production: When using multiple databases there are a few important settings. -First, the database name for the primary and replica should be the same because they contain -the same data. Second, the username for the primary and replica should be different, and the -replica user's permissions should be to read and not write. +First, the database name for the `primary` and `primary_replica` should be the same because they contain +the same data. This is also the case for `animals` and `animals_replica`. + +Second, the username for the writers and replicas should be different, and the +replica user's permissions should be set to only read and not write. When using a replica database you need to add a `replica: true` entry to the replica in the `database.yml`. This is because Rails otherwise has no way of knowing which one is a replica -and which one is the primary. +and which one is the writer. -Lastly, for new primary databases you need to set the `migrations_paths` to the directory +Lastly, for new writer databases you need to set the `migrations_paths` to the directory where you will store migrations for that database. We'll look more at `migrations_paths` later on in this guide. @@ -227,13 +236,13 @@ use a different parent class. Finally, in order to use the read-only replica in your application you'll need to activate the middleware for automatic switching. -Automatic switching allows the application to switch from the primary to replica or replica -to primary based on the HTTP verb and whether there was a recent write. +Automatic switching allows the application to switch from the writer to replica or replica +to writer based on the HTTP verb and whether there was a recent write. If the application is receiving a POST, PUT, DELETE, or PATCH request the application will -automatically write to the primary. For the specified time after the write, the 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. +automatically write to the writer database. For the specified time after the write, the +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. @@ -245,7 +254,7 @@ config.active_record.database_resolver_context = ActiveRecord::Middleware::Datab ``` Rails guarantees "read your own write" and will send your GET or HEAD request to the -primary if it's within the `delay` window. By default the delay is set to 2 seconds. You +writer if it's within the `delay` window. By default the delay is set to 2 seconds. You should change this based on your database infrastructure. Rails doesn't guarantee "read a recent write" for other users within the delay window and will send GET and HEAD requests to the replicas unless they wrote recently. @@ -274,7 +283,7 @@ config.active_record.database_resolver_context = MyCookieResolver ## Using manual connection switching -There are some cases where you may want your application to connect to a primary or a replica +There are some cases where you may want your application to connect to a writer or a replica and the automatic connection switching isn't adequate. For example, you may know that for a particular request you always want to send the request to a replica, even when you are in a POST request path. diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index 8fcf119637..6bd001e5c9 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -343,15 +343,12 @@ module ApplicationTests db_migrate_and_status database_url_db_name end - def db_schema_dump(database: nil) + def db_schema_dump Dir.chdir(app_path) do args = ["generate", "model", "book", "title:string"] - args << "--database=#{database}" if database rails args rails "db:migrate", "db:schema:dump" - dump_name = database ? "#{database}_schema.rb" : "schema.rb" - schema_dump = File.read("db/#{dump_name}") - assert_match(/create_table \"books\"/, schema_dump) + assert_match(/create_table \"books\"/, File.read("db/schema.rb")) end end @@ -427,17 +424,17 @@ module ApplicationTests statement_timeout: 1000 development: some_entry: - <<: *default - database: db/development_other.sqlite3 - migrations_paths: db/some_entry_migrate - primary: <<: *default database: db/development.sqlite3 + another_entry: + <<: *default + database: db/another_entry_development.sqlite3 + migrations_paths: db/another_entry_migrate YAML end end - db_schema_dump(database: "some_entry") + db_schema_dump db_schema_cache_dump end diff --git a/railties/test/application/rake/multi_dbs_test.rb b/railties/test/application/rake/multi_dbs_test.rb index f6a542e5ff..745c3bdacd 100644 --- a/railties/test/application/rake/multi_dbs_test.rb +++ b/railties/test/application/rake/multi_dbs_test.rb @@ -812,6 +812,21 @@ module ApplicationTests db_create_and_drop_namespace("primary", "db/development.sqlite3") end + + test "a thing" do + app_file "config/database.yml", <<-YAML + development: + default: + database: db/default.sqlite3 + adapter: sqlite3 + animals: + database: db/develoment_animals.sqlite3 + adapter: sqlite3 + migrations_paths: db/animals_migrate + YAML + + db_migrate_and_schema_dump_and_load + end end end end