mirror of https://github.com/rails/rails
Merge pull request #50140 from kmcphillips/ar-protocol-adapter
Add a `ActiveRecord.protocol_adapters` configuration to map `DATABASE_URL` protocols to adapters at an application level
This commit is contained in:
commit
9c22f35440
|
@ -1,3 +1,15 @@
|
||||||
|
* When using a `DATABASE_URL`, allow for a configuration to map the protocol in the URL to a specific database
|
||||||
|
adapter. This allows decoupling the adapter the application chooses to use from the database connection details
|
||||||
|
set in the deployment environment.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# ENV['DATABASE_URL'] = "mysql://localhost/example_database"
|
||||||
|
config.active_record.protocol_adapters.mysql = "trilogy"
|
||||||
|
# will connect to MySQL using the trilogy adapter
|
||||||
|
```
|
||||||
|
|
||||||
|
*Jean Boussier*, *Kevin McPhillips*
|
||||||
|
|
||||||
* In cases where MySQL returns `warning_count` greater than zero, but returns no warnings when
|
* In cases where MySQL returns `warning_count` greater than zero, but returns no warnings when
|
||||||
the `SHOW WARNINGS` query is executed, `ActiveRecord.db_warnings_action` proc will still be
|
the `SHOW WARNINGS` query is executed, `ActiveRecord.db_warnings_action` proc will still be
|
||||||
called with a generic warning message rather than silently ignoring the warning(s).
|
called with a generic warning message rather than silently ignoring the warning(s).
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
require "active_support"
|
require "active_support"
|
||||||
require "active_support/rails"
|
require "active_support/rails"
|
||||||
|
require "active_support/ordered_options"
|
||||||
require "active_model"
|
require "active_model"
|
||||||
require "arel"
|
require "arel"
|
||||||
require "yaml"
|
require "yaml"
|
||||||
|
@ -464,6 +465,34 @@ module ActiveRecord
|
||||||
Marshalling.format_version = value
|
Marshalling.format_version = value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# :singleton-method:
|
||||||
|
# Provides a mapping between database protocols/DBMSs and the
|
||||||
|
# underlying database adapter to be used. This is used only by the
|
||||||
|
# <tt>DATABASE_URL</tt> environment variable.
|
||||||
|
#
|
||||||
|
# == Example
|
||||||
|
#
|
||||||
|
# DATABASE_URL="mysql://myuser:mypass@localhost/somedatabase"
|
||||||
|
#
|
||||||
|
# The above URL specifies that MySQL is the desired protocol/DBMS, and the
|
||||||
|
# application configuration can then decide which adapter to use. For this example
|
||||||
|
# the default mapping is from <tt>mysql</tt> to <tt>mysql2</tt>, but <tt>:trilogy</tt>
|
||||||
|
# is also supported.
|
||||||
|
#
|
||||||
|
# ActiveRecord.protocol_adapters.mysql = "mysql2"
|
||||||
|
#
|
||||||
|
# The protocols names are arbitrary, and external database adapters can be
|
||||||
|
# registered and set here.
|
||||||
|
singleton_class.attr_accessor :protocol_adapters
|
||||||
|
self.protocol_adapters = ActiveSupport::InheritableOptions.new(
|
||||||
|
{
|
||||||
|
sqlite: "sqlite3",
|
||||||
|
mysql: "mysql2",
|
||||||
|
postgres: "postgresql",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def self.eager_load!
|
def self.eager_load!
|
||||||
super
|
super
|
||||||
ActiveRecord::Locking.eager_load!
|
ActiveRecord::Locking.eager_load!
|
||||||
|
|
|
@ -25,8 +25,7 @@ module ActiveRecord
|
||||||
def initialize(url)
|
def initialize(url)
|
||||||
raise "Database URL cannot be empty" if url.blank?
|
raise "Database URL cannot be empty" if url.blank?
|
||||||
@uri = uri_parser.parse(url)
|
@uri = uri_parser.parse(url)
|
||||||
@adapter = @uri.scheme && @uri.scheme.tr("-", "_")
|
@adapter = resolved_adapter
|
||||||
@adapter = "postgresql" if @adapter == "postgres"
|
|
||||||
|
|
||||||
if @uri.opaque
|
if @uri.opaque
|
||||||
@uri.opaque, @query = @uri.opaque.split("?", 2)
|
@uri.opaque, @query = @uri.opaque.split("?", 2)
|
||||||
|
@ -80,6 +79,12 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def resolved_adapter
|
||||||
|
adapter = uri.scheme && @uri.scheme.tr("-", "_")
|
||||||
|
adapter = ActiveRecord.protocol_adapters[adapter] || adapter
|
||||||
|
adapter
|
||||||
|
end
|
||||||
|
|
||||||
# Returns name of the database.
|
# Returns name of the database.
|
||||||
def database_from_path
|
def database_from_path
|
||||||
if @adapter == "sqlite3"
|
if @adapter == "sqlite3"
|
||||||
|
|
|
@ -10,6 +10,7 @@ module ActiveRecord
|
||||||
@previous_rack_env = ENV.delete("RACK_ENV")
|
@previous_rack_env = ENV.delete("RACK_ENV")
|
||||||
@previous_rails_env = ENV.delete("RAILS_ENV")
|
@previous_rails_env = ENV.delete("RAILS_ENV")
|
||||||
@adapters_was = ActiveRecord::ConnectionAdapters.instance_variable_get(:@adapters).dup
|
@adapters_was = ActiveRecord::ConnectionAdapters.instance_variable_get(:@adapters).dup
|
||||||
|
@protocol_adapters = ActiveRecord.protocol_adapters.dup
|
||||||
end
|
end
|
||||||
|
|
||||||
teardown do
|
teardown do
|
||||||
|
@ -17,6 +18,7 @@ module ActiveRecord
|
||||||
ENV["RACK_ENV"] = @previous_rack_env
|
ENV["RACK_ENV"] = @previous_rack_env
|
||||||
ENV["RAILS_ENV"] = @previous_rails_env
|
ENV["RAILS_ENV"] = @previous_rails_env
|
||||||
ActiveRecord::ConnectionAdapters.instance_variable_set(:@adapters, @adapters_was)
|
ActiveRecord::ConnectionAdapters.instance_variable_set(:@adapters, @adapters_was)
|
||||||
|
ActiveRecord.protocol_adapters = @protocol_adapters
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolve_config(config, env_name = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call)
|
def resolve_config(config, env_name = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call)
|
||||||
|
@ -434,6 +436,59 @@ module ActiveRecord
|
||||||
adapter: "postgresql",
|
adapter: "postgresql",
|
||||||
}, actual.configuration_hash)
|
}, actual.configuration_hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_protocol_adapter_mapping_is_used
|
||||||
|
ENV["DATABASE_URL"] = "mysql://localhost/exampledb"
|
||||||
|
ENV["RAILS_ENV"] = "production"
|
||||||
|
|
||||||
|
actual = resolve_db_config(:production, {})
|
||||||
|
expected = { adapter: "mysql2", database: "exampledb", host: "localhost" }
|
||||||
|
|
||||||
|
assert_equal expected, actual.configuration_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_protocol_adapter_mapping_falls_through_if_non_found
|
||||||
|
ENV["DATABASE_URL"] = "unknown://localhost/exampledb"
|
||||||
|
ENV["RAILS_ENV"] = "production"
|
||||||
|
|
||||||
|
actual = resolve_db_config(:production, {})
|
||||||
|
expected = { adapter: "unknown", database: "exampledb", host: "localhost" }
|
||||||
|
|
||||||
|
assert_equal expected, actual.configuration_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_protocol_adapter_mapping_is_used_and_can_be_updated
|
||||||
|
ActiveRecord.protocol_adapters.potato = "postgresql"
|
||||||
|
ENV["DATABASE_URL"] = "potato://localhost/exampledb"
|
||||||
|
ENV["RAILS_ENV"] = "production"
|
||||||
|
|
||||||
|
actual = resolve_db_config(:production, {})
|
||||||
|
expected = { adapter: "postgresql", database: "exampledb", host: "localhost" }
|
||||||
|
|
||||||
|
assert_equal expected, actual.configuration_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_protocol_adapter_mapping_translates_underscores_to_dashes
|
||||||
|
ActiveRecord.protocol_adapters.custom_protocol = "postgresql"
|
||||||
|
ENV["DATABASE_URL"] = "custom-protocol://localhost/exampledb"
|
||||||
|
ENV["RAILS_ENV"] = "production"
|
||||||
|
|
||||||
|
actual = resolve_db_config(:production, {})
|
||||||
|
expected = { adapter: "postgresql", database: "exampledb", host: "localhost" }
|
||||||
|
|
||||||
|
assert_equal expected, actual.configuration_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_protocol_adapter_mapping_handles_sqlite3_file_urls
|
||||||
|
ActiveRecord.protocol_adapters.custom_protocol = "sqlite3"
|
||||||
|
ENV["DATABASE_URL"] = "custom-protocol:/path/to/db.sqlite3"
|
||||||
|
ENV["RAILS_ENV"] = "production"
|
||||||
|
|
||||||
|
actual = resolve_db_config(:production, {})
|
||||||
|
expected = { adapter: "sqlite3", database: "/path/to/db.sqlite3" }
|
||||||
|
|
||||||
|
assert_equal expected, actual.configuration_hash
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1640,6 +1640,18 @@ The default value depends on the `config.load_defaults` target version:
|
||||||
| (original) | `true` |
|
| (original) | `true` |
|
||||||
| 7.1 | `false` |
|
| 7.1 | `false` |
|
||||||
|
|
||||||
|
#### `config.active_record.protocol_adapters`
|
||||||
|
|
||||||
|
When using a URL to configure the database connection, this option provides a mapping from the protocol to the underlying
|
||||||
|
database adapter. For example, this means the environment can specify `DATABASE_URL=mysql://localhost/database` and Rails will map
|
||||||
|
`mysql` to the `mysql2` adapter, but the application can also override these mappings:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
config.active_record.protocol_adapters.mysql = "trilogy"
|
||||||
|
```
|
||||||
|
|
||||||
|
If no mapping is found, the protocol is used as the adapter name.
|
||||||
|
|
||||||
### Configuring Action Controller
|
### Configuring Action Controller
|
||||||
|
|
||||||
`config.action_controller` includes a number of configuration settings:
|
`config.action_controller` includes a number of configuration settings:
|
||||||
|
@ -2950,6 +2962,10 @@ development:
|
||||||
|
|
||||||
The `config/database.yml` file can contain ERB tags `<%= %>`. Anything in the tags will be evaluated as Ruby code. You can use this to pull out data from an environment variable or to perform calculations to generate the needed connection information.
|
The `config/database.yml` file can contain ERB tags `<%= %>`. Anything in the tags will be evaluated as Ruby code. You can use this to pull out data from an environment variable or to perform calculations to generate the needed connection information.
|
||||||
|
|
||||||
|
When using a `ENV['DATABASE_URL']` or a `url` key in your `config/database.yml` file, Rails allows mapping the protocol
|
||||||
|
in the URL to a database adapter that can be configured from within the application. This allows the adapter to be configured
|
||||||
|
without modifying the URL set in the deployment environment. See: [`config.active_record.protocol_adapters`](#config-active_record-protocol-adapters).
|
||||||
|
|
||||||
|
|
||||||
TIP: You don't have to update the database configurations manually. If you look at the options of the application generator, you will see that one of the options is named `--database`. This option allows you to choose an adapter from a list of the most used relational databases. You can even run the generator repeatedly: `cd .. && rails new blog --database=mysql`. When you confirm the overwriting of the `config/database.yml` file, your application will be configured for MySQL instead of SQLite. Detailed examples of the common database connections are below.
|
TIP: You don't have to update the database configurations manually. If you look at the options of the application generator, you will see that one of the options is named `--database`. This option allows you to choose an adapter from a list of the most used relational databases. You can even run the generator repeatedly: `cd .. && rails new blog --database=mysql`. When you confirm the overwriting of the `config/database.yml` file, your application will be configured for MySQL instead of SQLite. Detailed examples of the common database connections are below.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue