Refactors Active Record connection management

While the three-tier config makes it easier to define databases for
multiple database applications, it quickly became clear to offer full
support for multiple databases we need to change the way the connections
hash was handled.

A three-tier config means that when Rails needed to choose a default
configuration (in the case a user doesn't ask for a specific
configuration) it wasn't clear to Rails which the default was. I
[bandaid fixed this so the rake tasks could work](#32271) but that fix
wasn't correct because it actually doubled up the configuration hashes.

Instead of attemping to manipulate the hashes @tenderlove and I decided
that it made more sense if we converted the hashes to objects so we can
easily ask those object questions. In a three tier config like this:

```
development:
  primary:
    database: "my_primary_db"
  animals:
    database; "my_animals_db"
```

We end up with an object like this:

```
  @configurations=[
    #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
      @env_name="development",@spec_name="primary",
      @config={"adapter"=>"sqlite3", "database"=>"db/development.sqlite3"}>,
    #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbdea90
      @env_name="development",@spec_name="animals",
      @config={"adapter"=>"sqlite3", "database"=>"db/development.sqlite3"}>
]>
```

The configurations setter takes the database configuration set by your
application and turns them into an
`ActiveRecord::DatabaseConfigurations` object that has one getter -
`@configurations` which is an array of all the database objects.

The configurations getter returns this object by default since it acts
like a hash in most of the cases we need. For example if you need to
access the default `development` database we can simply request it as we
did before:

```
ActiveRecord::Base.configurations["development"]
```

This will return primary development database configuration hash:

```
{ "database" => "my_primary_db" }
```

Internally all of Active Record has been converted to use the new
objects. I've built this to be backwards compatible but allow for
accessing the hash if needed for a deprecation period. To get the
original hash instead of the object you can either add `to_h` on the
configurations call or pass `legacy: true` to `configurations.

```
ActiveRecord::Base.configurations.to_h
=> { "development => { "database" => "my_primary_db" } }

ActiveRecord::Base.configurations(legacy: true)
=> { "development => { "database" => "my_primary_db" } }
```

The new configurations object allows us to iterate over the Active
Record configurations without losing the known environment or
specification name for that configuration. You can also select all the
configs for an env or env and spec. With this we can always ask
any object what environment it belongs to:

```
db_configs = ActiveRecord::Base.configurations.configurations_for("development")
=> #<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800
  @configurations=[
    #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
      @env_name="development",@spec_name="primary",
      @config={"adapter"=>"sqlite3", "database"=>"db/development.sqlite3"}>,
    #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbdea90
      @env_name="development",@spec_name="animals",
      @config={"adapter"=>"sqlite3", "database"=>"db/development.sqlite3"}>
]>

db_config.env_name
=> "development"

db_config.spec_name
=> "primary"

db_config.config
=> { "adapter"=>"sqlite3", "database"=>"db/development.sqlite3" }
```

The configurations object is more flexible than the configurations hash
and will allow us to build on top of the connection management in order
to add support for primary/replica connections, sharding, and
constructing queries for associations that live in multiple databases.
This commit is contained in:
Eileen Uchitelle 2018-08-16 15:49:18 -04:00
parent 3d2caab7dc
commit fdf3f0b930
21 changed files with 1196 additions and 259 deletions

View File

@ -1,3 +1,50 @@
* ActiveRecord::Base.configurations now returns an object.
ActiveRecord::Base.configurations used to return a hash, but this
is an inflexible data model. In order to improve multiple-database
handling in Rails, we've changed this to return an object. Some methods
are provided to make the object behave hash-like in order to ease the
transition process. Since most applications don't manipulate the hash
we've decided to add backwards-compatible functionality that will throw
a deprecation warning if used, however calling `ActiveRecord::Base.configurations`
will use the new version internally and externally.
For example, the following database.yml...
```
development:
adapter: sqlite3
database: db/development.sqlite3
```
Used to become a hash:
```
{ "development" => { "adapter" => "sqlite3", "database" => "db/development.sqlite3" } }
```
Is now converted into the following object:
```
#<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800 @configurations=[
#<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
@spec_name="primary", @config={"adapter"=>"sqlite3", "database"=>"db/development.sqlite3"}>
]
```
Iterating over the database configurations has also changed. Instead of
calling hash methods on the `configurations` hash directly, a new method `configs_for` has
been provided that allows you to select the correct configuration. `env_name` is a required
argument, `spec_name` is optional as well as passing a block. These return an array of
database config objects for the requested environment and specification name respectively.
```
ActiveRecord::Base.configurations.configs_for("development")
ActiveRecord::Base.configurations.configs_for("development", "primary")
```
*Eileen M. Uchitelle*, *Aaron Patterson*
* Add database configuration to disable advisory locks. * Add database configuration to disable advisory locks.
``` ```

View File

@ -40,7 +40,6 @@ module ActiveRecord
autoload :Core autoload :Core
autoload :ConnectionHandling autoload :ConnectionHandling
autoload :CounterCache autoload :CounterCache
autoload :DatabaseConfigurations
autoload :DynamicMatchers autoload :DynamicMatchers
autoload :Enum autoload :Enum
autoload :InternalMetadata autoload :InternalMetadata

View File

@ -22,6 +22,7 @@ require "active_record/explain_subscriber"
require "active_record/relation/delegation" require "active_record/relation/delegation"
require "active_record/attributes" require "active_record/attributes"
require "active_record/type_caster" require "active_record/type_caster"
require "active_record/database_configurations"
module ActiveRecord #:nodoc: module ActiveRecord #:nodoc:
# = Active Record # = Active Record
@ -291,7 +292,6 @@ module ActiveRecord #:nodoc:
extend Aggregations::ClassMethods extend Aggregations::ClassMethods
include Core include Core
include DatabaseConfigurations
include Persistence include Persistence
include ReadonlyAttributes include ReadonlyAttributes
include ModelSchema include ModelSchema

View File

@ -114,8 +114,7 @@ module ActiveRecord
class Resolver # :nodoc: class Resolver # :nodoc:
attr_reader :configurations attr_reader :configurations
# Accepts a hash two layers deep, keys on the first layer represent # Accepts a list of db config objects.
# environments such as "production". Keys must be strings.
def initialize(configurations) def initialize(configurations)
@configurations = configurations @configurations = configurations
end end
@ -136,33 +135,14 @@ module ActiveRecord
# Resolver.new(configurations).resolve(:production) # Resolver.new(configurations).resolve(:production)
# # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
# #
def resolve(config) def resolve(config_or_env, pool_name = nil)
if config if config_or_env
resolve_connection config resolve_connection config_or_env, pool_name
elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call
resolve_symbol_connection env.to_sym
else else
raise AdapterNotSpecified raise AdapterNotSpecified
end end
end end
# Expands each key in @configurations hash into fully resolved hash
def resolve_all
config = configurations.dup
if env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
env_config = config[env] if config[env].is_a?(Hash) && !(config[env].key?("adapter") || config[env].key?("url"))
end
config.merge! env_config if env_config
config.each do |key, value|
config[key] = resolve(value) if value
end
config
end
# Returns an instance of ConnectionSpecification for a given adapter. # Returns an instance of ConnectionSpecification for a given adapter.
# Accepts a hash one layer deep that contains all connection information. # Accepts a hash one layer deep that contains all connection information.
# #
@ -176,7 +156,9 @@ module ActiveRecord
# # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } # # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" }
# #
def spec(config) def spec(config)
spec = resolve(config).symbolize_keys pool_name = config if config.is_a?(Symbol)
spec = resolve(config, pool_name).symbolize_keys
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter) raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
@ -211,7 +193,6 @@ module ActiveRecord
end end
private private
# Returns fully resolved connection, accepts hash, string or symbol. # Returns fully resolved connection, accepts hash, string or symbol.
# Always returns a hash. # Always returns a hash.
# #
@ -232,29 +213,42 @@ module ActiveRecord
# Resolver.new({}).resolve_connection("postgresql://localhost/foo") # Resolver.new({}).resolve_connection("postgresql://localhost/foo")
# # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
# #
def resolve_connection(spec) def resolve_connection(config_or_env, pool_name = nil)
case spec case config_or_env
when Symbol when Symbol
resolve_symbol_connection spec resolve_symbol_connection config_or_env, pool_name
when String when String
resolve_url_connection spec resolve_url_connection config_or_env
when Hash when Hash
resolve_hash_connection spec resolve_hash_connection config_or_env
else
resolve_connection config_or_env
end end
end end
# Takes the environment such as +:production+ or +:development+. # Takes the environment such as +:production+ or +:development+ and a
# pool name the corresponds to the name given by the connection pool
# to the connection. That pool name is merged into the hash with the
# name key.
#
# This requires that the @configurations was initialized with a key that # This requires that the @configurations was initialized with a key that
# matches. # matches.
# #
# Resolver.new("production" => {}).resolve_symbol_connection(:production) # configurations = #<ActiveRecord::DatabaseConfigurations:0x00007fd9fdace3e0
# # => {} # @configurations=[
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd9fdace250
# @env_name="production", @spec_name="primary", @config={"database"=>"my_db"}>
# ]>
# #
def resolve_symbol_connection(spec) # Resolver.new(configurations).resolve_symbol_connection(:production, "primary")
if config = configurations[spec.to_s] # # => { "database" => "my_db" }
resolve_connection(config).merge("name" => spec.to_s) def resolve_symbol_connection(env_name, pool_name)
db_config = configurations.find_db_config(env_name)
if db_config
resolve_connection(db_config.config).merge("name" => pool_name.to_s)
else else
raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}") raise(AdapterNotSpecified, "'#{env_name}' database is not configured. Available: #{configurations.configurations.map(&:env_name).join(", ")}")
end end
end end

View File

@ -46,45 +46,18 @@ module ActiveRecord
# #
# The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+ # The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+
# may be returned on an error. # may be returned on an error.
def establish_connection(config = nil) def establish_connection(config_or_env = nil)
raise "Anonymous class is not allowed." unless name raise "Anonymous class is not allowed." unless name
config ||= DEFAULT_ENV.call.to_sym config_or_env ||= DEFAULT_ENV.call.to_sym
spec_name = self == Base ? "primary" : name pool_name = self == Base ? "primary" : name
self.connection_specification_name = spec_name self.connection_specification_name = pool_name
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations) resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
spec = resolver.resolve(config).symbolize_keys config_hash = resolver.resolve(config_or_env, pool_name).symbolize_keys
spec[:name] = spec_name config_hash[:name] = pool_name
# use the primary config if a config is not passed in and connection_handler.establish_connection(config_hash)
# it's a three tier config
spec = spec[spec_name.to_sym] if spec[spec_name.to_sym]
connection_handler.establish_connection(spec)
end
class MergeAndResolveDefaultUrlConfig # :nodoc:
def initialize(raw_configurations)
@raw_config = raw_configurations.dup
@env = DEFAULT_ENV.call.to_s
end
# Returns fully resolved connection hashes.
# Merges connection information from `ENV['DATABASE_URL']` if available.
def resolve
ConnectionAdapters::ConnectionSpecification::Resolver.new(config).resolve_all
end
private
def config
@raw_config.dup.tap do |cfg|
if url = ENV["DATABASE_URL"]
cfg[@env] ||= {}
cfg[@env]["url"] ||= url
end
end
end
end end
# Returns the connection currently associated with the class. This can # Returns the connection currently associated with the class. This can

View File

@ -26,7 +26,7 @@ module ActiveRecord
## ##
# Contains the database configuration - as is typically stored in config/database.yml - # Contains the database configuration - as is typically stored in config/database.yml -
# as a Hash. # as an ActiveRecord::DatabaseConfigurations object.
# #
# For example, the following database.yml... # For example, the following database.yml...
# #
@ -40,22 +40,18 @@ module ActiveRecord
# #
# ...would result in ActiveRecord::Base.configurations to look like this: # ...would result in ActiveRecord::Base.configurations to look like this:
# #
# { # #<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800 @configurations=[
# 'development' => { # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
# 'adapter' => 'sqlite3', # @spec_name="primary", @config={"adapter"=>"sqlite3", "database"=>"db/development.sqlite3"}>,
# 'database' => 'db/development.sqlite3' # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbdea90 @env_name="production",
# }, # @spec_name="primary", @config={"adapter"=>"mysql2", "database"=>"db/production.sqlite3"}>
# 'production' => { # ]>
# 'adapter' => 'sqlite3',
# 'database' => 'db/production.sqlite3'
# }
# }
def self.configurations=(config) def self.configurations=(config)
@@configurations = ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve @@configurations = ActiveRecord::DatabaseConfigurations.new(config)
end end
self.configurations = {} self.configurations = {}
# Returns fully resolved configurations hash # Returns fully resolved ActiveRecord::DatabaseConfigurations object
def self.configurations def self.configurations
@@configurations @@configurations
end end

View File

@ -1,63 +1,168 @@
# frozen_string_literal: true # frozen_string_literal: true
require "active_record/database_configurations/database_config"
require "active_record/database_configurations/hash_config"
require "active_record/database_configurations/url_config"
module ActiveRecord module ActiveRecord
module DatabaseConfigurations # :nodoc: # ActiveRecord::DatabaseConfigurations returns an array of DatabaseConfig
class DatabaseConfig # objects (either a HashConfig or UrlConfig) that are constructed from the
attr_reader :env_name, :spec_name, :config # application's database configuration hash or url string.
class DatabaseConfigurations
attr_reader :configurations
def initialize(env_name, spec_name, config) def initialize(configurations = {})
@env_name = env_name @configurations = build_configs(configurations)
@spec_name = spec_name
@config = config
end
end end
# Selects the config for the specified environment and specification name # Collects the configs for the environment and optionally the specification
# name passed in.
# #
# For example if passed :development, and :animals it will select the database # If a spec name is provided a single DatabaseConfiguration object will be
# under the :development and :animals configuration level # returned, otherwise an array of DatabaseConfiguration objects will be
def self.config_for_env_and_spec(environment, specification_name, configs = ActiveRecord::Base.configurations) # :nodoc: # returned that corresponds with the environment requested.
configs_for(environment, configs).find do |db_config| def configs_for(env = nil, spec = nil, &blk)
db_config.spec_name == specification_name configs = env_with_configs(env)
end
end
# Collects the configs for the environment passed in. if spec
# configs.find do |db_config|
# If a block is given returns the specification name and configuration db_config.spec_name == spec
# otherwise returns an array of DatabaseConfig structs for the environment.
def self.configs_for(env, configs = ActiveRecord::Base.configurations, &blk) # :nodoc:
env_with_configs = db_configs(configs).select do |db_config|
db_config.env_name == env
end
if block_given?
env_with_configs.each do |env_with_config|
yield env_with_config.spec_name, env_with_config.config
end end
else else
env_with_configs configs
end end
end end
# Given an env, spec and config creates DatabaseConfig structs with # Returns the config hash that corresponds with the environment
# each attribute set. #
def self.walk_configs(env_name, spec_name, config) # :nodoc: # If the application has multiple databases `default_hash` will
if config["database"] || config["url"] || config["adapter"] # the first config hash for the environment.
DatabaseConfig.new(env_name, spec_name, config) #
else # { database: "my_db", adapter: "mysql2" }
config.each_pair.map do |sub_spec_name, sub_config| def default_hash(env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s)
walk_configs(env_name, sub_spec_name, sub_config) default = find_db_config(env)
default.config if default
end
alias :[] :default_hash
# Returns a single DatabaseConfig object based on the requested environment.
#
# If the application has multiple databases `select_db_config` will return
# the first DatabaseConfig for the environment.
def find_db_config(env)
configurations.find do |db_config|
db_config.env_name == env.to_s ||
(db_config.for_current_env? && db_config.spec_name == env.to_s)
end
end
# Returns the DatabaseConfig object as a Hash.
def to_h
configs = configurations.reverse.inject({}) do |memo, db_config|
memo.merge(db_config.to_legacy_hash)
end
Hash[configs.to_a.reverse]
end
# Checks if the application's configurations are empty.
#
# Aliased to blank?
def empty?
configurations.empty?
end
alias :blank? :empty?
private
def env_with_configs(env = nil)
if env
configurations.select { |db_config| db_config.env_name == env }
else
configurations
end end
end end
end
# Walks all the configs passed in and returns an array def build_configs(configs)
# of DatabaseConfig structs for each configuration. return configs.configurations if configs.is_a?(DatabaseConfigurations)
def self.db_configs(configs = ActiveRecord::Base.configurations) # :nodoc:
configs.each_pair.flat_map do |env_name, config| build_db_config = configs.each_pair.flat_map do |env_name, config|
walk_configs(env_name, "primary", config) walk_configs(env_name, "primary", config)
end.compact
if url = ENV["DATABASE_URL"]
build_url_config(url, build_db_config)
else
build_db_config
end
end
def walk_configs(env_name, spec_name, config)
case config
when String
build_db_config_from_string(env_name, spec_name, config)
when Hash
build_db_config_from_hash(env_name, spec_name, config)
end
end
def build_db_config_from_string(env_name, spec_name, config)
begin
url = config
uri = URI.parse(url)
if uri.try(:scheme)
ActiveRecord::DatabaseConfigurations::UrlConfig.new(env_name, spec_name, url)
end
rescue URI::InvalidURIError
ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
end
end
def build_db_config_from_hash(env_name, spec_name, config)
if url = config["url"]
config_without_url = config.dup
config_without_url.delete "url"
ActiveRecord::DatabaseConfigurations::UrlConfig.new(env_name, spec_name, url, config_without_url)
elsif config["database"] || (config.size == 1 && config.values.all? { |v| v.is_a? String })
ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
else
config.each_pair.map do |sub_spec_name, sub_config|
walk_configs(env_name, sub_spec_name, sub_config)
end
end
end
def build_url_config(url, configs)
env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
if original_config = configs.find(&:for_current_env?)
if original_config.url_config?
configs
else
configs.map do |config|
ActiveRecord::DatabaseConfigurations::UrlConfig.new(env, config.spec_name, url, config.config)
end
end
else
configs + [ActiveRecord::DatabaseConfigurations::UrlConfig.new(env, "primary", url)]
end
end
def method_missing(method, *args, &blk)
if Hash.method_defined?(method)
ActiveSupport::Deprecation.warn \
"Returning a hash from ActiveRecord::Base.configurations is deprecated. Therefore calling `#{method}` on the hash is also deprecated. Please switch to using the `configs_for` method instead to collect and iterate over database configurations."
end
case method
when :each, :first
configurations.send(method, *args, &blk)
when :fetch
configs_for(args.first)
when :values
configurations.map(&:config)
else
super
end
end end
end
end end
end end

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
module ActiveRecord
class DatabaseConfigurations
# ActiveRecord::Base.configurations will return either a HashConfig or
# UrlConfig respectively. It will never return a DatabaseConfig object,
# as this is the parent class for the types of database configuration objects.
class DatabaseConfig # :nodoc:
attr_reader :env_name, :spec_name
def initialize(env_name, spec_name)
@env_name = env_name
@spec_name = spec_name
end
def url_config?
false
end
def to_legacy_hash
{ env_name => config }
end
def for_current_env?
env_name == ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
end
end
end
end

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
module ActiveRecord
class DatabaseConfigurations
# A HashConfig object is created for each database configuration entry that
# is created from a hash.
#
# A hash config:
#
# { "development" => { "database" => "db_name" } }
#
# Becomes:
#
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
# @env_name="development", @spec_name="primary", @config={"db_name"}>
#
# Options are:
#
# <tt>:env_name</tt> - The Rails environment, ie "development"
# <tt>:spec_name</tt> - The specification name. In a standard two-tier
# database configuration this will default to "primary". In a multiple
# database three-tier database configuration this corresponds to the name
# used in the second tier, for example "primary_readonly".
# <tt>:config</tt> - The config hash. This is the hash that contains the
# database adapter, name, and other important information for database
# connections.
class HashConfig < DatabaseConfig
attr_reader :config
def initialize(env_name, spec_name, config)
super(env_name, spec_name)
@config = config
end
end
end
end

View File

@ -0,0 +1,60 @@
# frozen_string_literal: true
module ActiveRecord
class DatabaseConfigurations
# A UrlConfig object is created for each database configuration
# entry that is created from a URL. This can either be a URL string
# or a hash with a URL in place of the config hash.
#
# A URL config:
#
# postgres://localhost/foo
#
# Becomes:
#
# #<ActiveRecord::DatabaseConfigurations::UrlConfig:0x00007fdc3238f340
# @env_name="default_env", @spec_name="primary",
# @config={"adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost"},
# @url="postgres://localhost/foo">
#
# Options are:
#
# <tt>:env_name</tt> - The Rails environment, ie "development"
# <tt>:spec_name</tt> - The specification name. In a standard two-tier
# database configuration this will default to "primary". In a multiple
# database three-tier database configuration this corresponds to the name
# used in the second tier, for example "primary_readonly".
# <tt>:url</tt> - The database URL.
# <tt>:config</tt> - The config hash. This is the hash that contains the
# database adapter, name, and other important information for database
# connections.
class UrlConfig < DatabaseConfig
attr_reader :url, :config
def initialize(env_name, spec_name, url, config = {})
super(env_name, spec_name)
@config = build_config(config, url)
@url = url
end
def url_config? # :nodoc:
true
end
private
def build_config(original_config, url)
if /^jdbc:/.match?(url)
hash = { "url" => url }
else
hash = ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(url).to_hash
end
if original_config[env_name]
original_config[env_name].merge(hash)
else
original_config.merge(hash)
end
end
end
end
end

View File

@ -26,7 +26,7 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name| ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
desc "Create #{spec_name} database for current environment" desc "Create #{spec_name} database for current environment"
task spec_name => :load_config do task spec_name => :load_config do
db_config = ActiveRecord::DatabaseConfigurations.config_for_env_and_spec(Rails.env, spec_name) db_config = ActiveRecord::Base.configurations.configs_for(Rails.env, spec_name)
ActiveRecord::Tasks::DatabaseTasks.create(db_config.config) ActiveRecord::Tasks::DatabaseTasks.create(db_config.config)
end end
end end
@ -45,7 +45,7 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name| ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
desc "Drop #{spec_name} database for current environment" desc "Drop #{spec_name} database for current environment"
task spec_name => [:load_config, :check_protected_environments] do task spec_name => [:load_config, :check_protected_environments] do
db_config = ActiveRecord::DatabaseConfigurations.config_for_env_and_spec(Rails.env, spec_name) db_config = ActiveRecord::Base.configurations.configs_for(Rails.env, spec_name)
ActiveRecord::Tasks::DatabaseTasks.drop(db_config.config) ActiveRecord::Tasks::DatabaseTasks.drop(db_config.config)
end end
end end
@ -73,8 +73,8 @@ db_namespace = namespace :db do
desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)." desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
task migrate: :load_config do task migrate: :load_config do
ActiveRecord::DatabaseConfigurations.configs_for(Rails.env) do |spec_name, config| ActiveRecord::Base.configurations.configs_for(Rails.env).each do |db_config|
ActiveRecord::Base.establish_connection(config) ActiveRecord::Base.establish_connection(db_config.config)
ActiveRecord::Tasks::DatabaseTasks.migrate ActiveRecord::Tasks::DatabaseTasks.migrate
end end
db_namespace["_dump"].invoke db_namespace["_dump"].invoke
@ -99,7 +99,7 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name| ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
desc "Migrate #{spec_name} database for current environment" desc "Migrate #{spec_name} database for current environment"
task spec_name => :load_config do task spec_name => :load_config do
db_config = ActiveRecord::DatabaseConfigurations.config_for_env_and_spec(Rails.env, spec_name) db_config = ActiveRecord::Base.configurations.configs_for(Rails.env, spec_name)
ActiveRecord::Base.establish_connection(db_config.config) ActiveRecord::Base.establish_connection(db_config.config)
ActiveRecord::Tasks::DatabaseTasks.migrate ActiveRecord::Tasks::DatabaseTasks.migrate
end end
@ -274,11 +274,10 @@ db_namespace = namespace :db do
desc "Creates a db/schema.rb file that is portable against any DB supported by Active Record" desc "Creates a db/schema.rb file that is portable against any DB supported by Active Record"
task dump: :load_config do task dump: :load_config do
require "active_record/schema_dumper" require "active_record/schema_dumper"
ActiveRecord::Base.configurations.configs_for(Rails.env).each do |db_config|
ActiveRecord::DatabaseConfigurations.configs_for(Rails.env) do |spec_name, config| filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.spec_name, :ruby)
filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(spec_name, :ruby)
File.open(filename, "w:utf-8") do |file| File.open(filename, "w:utf-8") do |file|
ActiveRecord::Base.establish_connection(config) ActiveRecord::Base.establish_connection(db_config.config)
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
end end
end end
@ -314,11 +313,10 @@ db_namespace = namespace :db do
namespace :structure do namespace :structure do
desc "Dumps the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql" desc "Dumps the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql"
task dump: :load_config do task dump: :load_config do
ActiveRecord::DatabaseConfigurations.configs_for(Rails.env) do |spec_name, config| ActiveRecord::Base.configurations.configs_for(Rails.env).each do |db_config|
ActiveRecord::Base.establish_connection(config) ActiveRecord::Base.establish_connection(db_config.config)
filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(spec_name, :sql) filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.spec_name, :sql)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(config, filename) ActiveRecord::Tasks::DatabaseTasks.structure_dump(db_config.config, filename)
if ActiveRecord::SchemaMigration.table_exists? if ActiveRecord::SchemaMigration.table_exists?
File.open(filename, "a") do |f| File.open(filename, "a") do |f|
f.puts ActiveRecord::Base.connection.dump_schema_information f.puts ActiveRecord::Base.connection.dump_schema_information
@ -356,22 +354,30 @@ db_namespace = namespace :db do
begin begin
should_reconnect = ActiveRecord::Base.connection_pool.active_connection? should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
ActiveRecord::Schema.verbose = false ActiveRecord::Schema.verbose = false
ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :ruby, ENV["SCHEMA"], "test" ActiveRecord::Base.configurations.configs_for("test").each do |db_config|
filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.spec_name, :ruby)
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config.config, :ruby, filename, "test")
end
ensure ensure
if should_reconnect if should_reconnect
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env]) ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations.default_hash(ActiveRecord::Tasks::DatabaseTasks.env))
end end
end end
end end
# desc "Recreate the test database from an existent structure.sql file" # desc "Recreate the test database from an existent structure.sql file"
task load_structure: %w(db:test:purge) do task load_structure: %w(db:test:purge) do
ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :sql, ENV["SCHEMA"], "test" ActiveRecord::Base.configurations.configs_for("test").each do |db_config|
filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.spec_name, :sql)
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config.config, :sql, filename, "test")
end
end end
# desc "Empty the test database" # desc "Empty the test database"
task purge: %w(load_config check_protected_environments) do task purge: %w(load_config check_protected_environments) do
ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations["test"] ActiveRecord::Base.configurations.configs_for("test").each do |db_config|
ActiveRecord::Tasks::DatabaseTasks.purge(db_config.config)
end
end end
# desc 'Load the test schema' # desc 'Load the test schema'

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require "active_record/database_configurations"
module ActiveRecord module ActiveRecord
module Tasks # :nodoc: module Tasks # :nodoc:
class DatabaseAlreadyExists < StandardError; end # :nodoc: class DatabaseAlreadyExists < StandardError; end # :nodoc:
@ -101,16 +103,21 @@ module ActiveRecord
@env ||= Rails.env @env ||= Rails.env
end end
def spec
@spec ||= "primary"
end
def seed_loader def seed_loader
@seed_loader ||= Rails.application @seed_loader ||= Rails.application
end end
def current_config(options = {}) def current_config(options = {})
options.reverse_merge! env: env options.reverse_merge! env: env
options[:spec] ||= "primary"
if options.has_key?(:config) if options.has_key?(:config)
@current_config = options[:config] @current_config = options[:config]
else else
@current_config ||= ActiveRecord::Base.configurations[options[:env]] @current_config ||= ActiveRecord::Base.configurations.configs_for(options[:env], options[:spec]).config
end end
end end
@ -136,7 +143,7 @@ module ActiveRecord
def for_each def for_each
databases = Rails.application.config.database_configuration databases = Rails.application.config.database_configuration
database_configs = ActiveRecord::DatabaseConfigurations.configs_for(Rails.env, databases) database_configs = ActiveRecord::DatabaseConfigurations.new(databases).configs_for(Rails.env)
# if this is a single database application we don't want tasks for each primary database # if this is a single database application we don't want tasks for each primary database
return if database_configs.count == 1 return if database_configs.count == 1
@ -180,9 +187,11 @@ module ActiveRecord
scope = ENV["SCOPE"] scope = ENV["SCOPE"]
verbose_was, Migration.verbose = Migration.verbose, verbose? verbose_was, Migration.verbose = Migration.verbose, verbose?
Base.connection.migration_context.migrate(target_version) do |migration| Base.connection.migration_context.migrate(target_version) do |migration|
scope.blank? || scope == migration.scope scope.blank? || scope == migration.scope
end end
ActiveRecord::Base.clear_cache! ActiveRecord::Base.clear_cache!
ensure ensure
Migration.verbose = verbose_was Migration.verbose = verbose_was
@ -198,8 +207,8 @@ module ActiveRecord
ENV["VERSION"].to_i if ENV["VERSION"] && !ENV["VERSION"].empty? ENV["VERSION"].to_i if ENV["VERSION"] && !ENV["VERSION"].empty?
end end
def charset_current(environment = env) def charset_current(environment = env, specification_name = spec)
charset ActiveRecord::Base.configurations[environment] charset ActiveRecord::Base.configurations.configs_for(environment, specification_name).config
end end
def charset(*arguments) def charset(*arguments)
@ -207,8 +216,8 @@ module ActiveRecord
class_for_adapter(configuration["adapter"]).new(*arguments).charset class_for_adapter(configuration["adapter"]).new(*arguments).charset
end end
def collation_current(environment = env) def collation_current(environment = env, specification_name = spec)
collation ActiveRecord::Base.configurations[environment] collation ActiveRecord::Base.configurations.configs_for(environment, specification_name).config
end end
def collation(*arguments) def collation(*arguments)
@ -342,14 +351,15 @@ module ActiveRecord
environments << "test" if environment == "development" environments << "test" if environment == "development"
environments.each do |env| environments.each do |env|
ActiveRecord::DatabaseConfigurations.configs_for(env) do |spec_name, configuration| ActiveRecord::Base.configurations.configs_for(env).each do |db_config|
yield configuration, spec_name, env yield db_config.config, db_config.spec_name, env
end end
end end
end end
def each_local_configuration def each_local_configuration
ActiveRecord::Base.configurations.each_value do |configuration| ActiveRecord::Base.configurations.configs_for.each do |db_config|
configuration = db_config.config
next unless configuration["database"] next unless configuration["database"]
if local_database?(configuration) if local_database?(configuration)

View File

@ -5,31 +5,32 @@ require "active_support/testing/parallelization"
module ActiveRecord module ActiveRecord
module TestDatabases # :nodoc: module TestDatabases # :nodoc:
ActiveSupport::Testing::Parallelization.after_fork_hook do |i| ActiveSupport::Testing::Parallelization.after_fork_hook do |i|
create_and_load_schema(i, spec_name: Rails.env) create_and_load_schema(i, env_name: Rails.env)
end end
ActiveSupport::Testing::Parallelization.run_cleanup_hook do |_| ActiveSupport::Testing::Parallelization.run_cleanup_hook do
drop(spec_name: Rails.env) drop(env_name: Rails.env)
end end
def self.create_and_load_schema(i, spec_name:) def self.create_and_load_schema(i, env_name:)
old, ENV["VERBOSE"] = ENV["VERBOSE"], "false" old, ENV["VERBOSE"] = ENV["VERBOSE"], "false"
connection_spec = ActiveRecord::Base.configurations[spec_name] ActiveRecord::Base.configurations.configs_for(env_name).each do |db_config|
db_config.config["database"] += "-#{i}"
connection_spec["database"] += "-#{i}" ActiveRecord::Tasks::DatabaseTasks.create(db_config.config)
ActiveRecord::Tasks::DatabaseTasks.create(connection_spec) ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config.config, ActiveRecord::Base.schema_format, nil, env_name, db_config.spec_name)
ActiveRecord::Tasks::DatabaseTasks.load_schema(connection_spec) end
ensure ensure
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[Rails.env]) ActiveRecord::Base.establish_connection(Rails.env.to_sym)
ENV["VERBOSE"] = old ENV["VERBOSE"] = old
end end
def self.drop(spec_name:) def self.drop(env_name:)
old, ENV["VERBOSE"] = ENV["VERBOSE"], "false" old, ENV["VERBOSE"] = ENV["VERBOSE"], "false"
connection_spec = ActiveRecord::Base.configurations[spec_name]
ActiveRecord::Tasks::DatabaseTasks.drop(connection_spec) ActiveRecord::Base.configurations.configs_for(env_name).each do |db_config|
ActiveRecord::Tasks::DatabaseTasks.drop(db_config.config)
end
ensure ensure
ENV["VERBOSE"] = old ENV["VERBOSE"] = old
end end

View File

@ -28,13 +28,16 @@ module ActiveRecord
end end
def test_establish_connection_uses_spec_name def test_establish_connection_uses_spec_name
old_config = ActiveRecord::Base.configurations
config = { "readonly" => { "adapter" => "sqlite3" } } config = { "readonly" => { "adapter" => "sqlite3" } }
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(config) ActiveRecord::Base.configurations = config
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(ActiveRecord::Base.configurations)
spec = resolver.spec(:readonly) spec = resolver.spec(:readonly)
@handler.establish_connection(spec.to_hash) @handler.establish_connection(spec.to_hash)
assert_not_nil @handler.retrieve_connection_pool("readonly") assert_not_nil @handler.retrieve_connection_pool("readonly")
ensure ensure
ActiveRecord::Base.configurations = old_config
@handler.remove_connection("readonly") @handler.remove_connection("readonly")
end end

View File

@ -18,11 +18,14 @@ module ActiveRecord
end end
def resolve_config(config) def resolve_config(config)
ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve configs = ActiveRecord::DatabaseConfigurations.new(config)
configs.to_h
end end
def resolve_spec(spec, config) def resolve_spec(spec, config)
ConnectionSpecification::Resolver.new(resolve_config(config)).resolve(spec) configs = ActiveRecord::DatabaseConfigurations.new(config)
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
resolver.resolve(spec, spec)
end end
def test_resolver_with_database_uri_and_current_env_symbol_key def test_resolver_with_database_uri_and_current_env_symbol_key

View File

@ -7,11 +7,15 @@ module ActiveRecord
class ConnectionSpecification class ConnectionSpecification
class ResolverTest < ActiveRecord::TestCase class ResolverTest < ActiveRecord::TestCase
def resolve(spec, config = {}) def resolve(spec, config = {})
Resolver.new(config).resolve(spec) configs = ActiveRecord::DatabaseConfigurations.new(config)
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
resolver.resolve(spec, spec)
end end
def spec(spec, config = {}) def spec(spec, config = {})
Resolver.new(config).spec(spec) configs = ActiveRecord::DatabaseConfigurations.new(config)
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
resolver.spec(spec)
end end
def test_url_invalid_adapter def test_url_invalid_adapter

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
require "cases/helper"
module ActiveRecord
class LegacyConfigurationsTest < ActiveRecord::TestCase
def test_can_turn_configurations_into_a_hash
assert ActiveRecord::Base.configurations.to_h.is_a?(Hash), "expected to be a hash but was not."
assert_equal ["arunit", "arunit2", "arunit_without_prepared_statements"].sort, ActiveRecord::Base.configurations.to_h.keys.sort
end
def test_each_is_deprecated
assert_deprecated do
ActiveRecord::Base.configurations.each do |db_config|
assert_equal "primary", db_config.spec_name
end
end
end
def test_first_is_deprecated
assert_deprecated do
db_config = ActiveRecord::Base.configurations.first
assert_equal "arunit", db_config.env_name
assert_equal "primary", db_config.spec_name
end
end
def test_fetch_is_deprecated
assert_deprecated do
db_config = ActiveRecord::Base.configurations.fetch("arunit").first
assert_equal "arunit", db_config.env_name
assert_equal "primary", db_config.spec_name
end
end
def test_values_are_deprecated
config_hashes = ActiveRecord::Base.configurations.configurations.map(&:config)
assert_deprecated do
assert_equal config_hashes, ActiveRecord::Base.configurations.values
end
end
end
end

View File

@ -156,20 +156,24 @@ module ActiveRecord
class DatabaseTasksCreateAllTest < ActiveRecord::TestCase class DatabaseTasksCreateAllTest < ActiveRecord::TestCase
def setup def setup
@old_configurations = ActiveRecord::Base.configurations
@configurations = { "development" => { "database" => "my-db" } } @configurations = { "development" => { "database" => "my-db" } }
$stdout, @original_stdout = StringIO.new, $stdout $stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr $stderr, @original_stderr = StringIO.new, $stderr
ActiveRecord::Base.configurations = @configurations
end end
def teardown def teardown
$stdout, $stderr = @original_stdout, @original_stderr $stdout, $stderr = @original_stdout, @original_stderr
ActiveRecord::Base.configurations = @old_configurations
end end
def test_ignores_configurations_without_databases def test_ignores_configurations_without_databases
@configurations["development"]["database"] = nil @configurations["development"]["database"] = nil
with_stubbed_configurations_establish_connection do ActiveRecord::Base.configurations do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
ActiveRecord::Tasks::DatabaseTasks.create_all ActiveRecord::Tasks::DatabaseTasks.create_all
end end
@ -179,7 +183,7 @@ module ActiveRecord
def test_ignores_remote_databases def test_ignores_remote_databases
@configurations["development"]["host"] = "my.server.tld" @configurations["development"]["host"] = "my.server.tld"
with_stubbed_configurations_establish_connection do ActiveRecord::Base.configurations do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
ActiveRecord::Tasks::DatabaseTasks.create_all ActiveRecord::Tasks::DatabaseTasks.create_all
end end
@ -189,7 +193,7 @@ module ActiveRecord
def test_warning_for_remote_databases def test_warning_for_remote_databases
@configurations["development"]["host"] = "my.server.tld" @configurations["development"]["host"] = "my.server.tld"
with_stubbed_configurations_establish_connection do ActiveRecord::Base.configurations do
ActiveRecord::Tasks::DatabaseTasks.create_all ActiveRecord::Tasks::DatabaseTasks.create_all
assert_match "This task only modifies local databases. my-db is on a remote host.", assert_match "This task only modifies local databases. my-db is on a remote host.",
@ -200,7 +204,7 @@ module ActiveRecord
def test_creates_configurations_with_local_ip def test_creates_configurations_with_local_ip
@configurations["development"]["host"] = "127.0.0.1" @configurations["development"]["host"] = "127.0.0.1"
with_stubbed_configurations_establish_connection do ActiveRecord::Base.configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
ActiveRecord::Tasks::DatabaseTasks.create_all ActiveRecord::Tasks::DatabaseTasks.create_all
end end
@ -210,7 +214,7 @@ module ActiveRecord
def test_creates_configurations_with_local_host def test_creates_configurations_with_local_host
@configurations["development"]["host"] = "localhost" @configurations["development"]["host"] = "localhost"
with_stubbed_configurations_establish_connection do ActiveRecord::Base.configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
ActiveRecord::Tasks::DatabaseTasks.create_all ActiveRecord::Tasks::DatabaseTasks.create_all
end end
@ -220,40 +224,33 @@ module ActiveRecord
def test_creates_configurations_with_blank_hosts def test_creates_configurations_with_blank_hosts
@configurations["development"]["host"] = nil @configurations["development"]["host"] = nil
with_stubbed_configurations_establish_connection do ActiveRecord::Base.configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
ActiveRecord::Tasks::DatabaseTasks.create_all ActiveRecord::Tasks::DatabaseTasks.create_all
end end
end end
end end
private
def with_stubbed_configurations_establish_connection
ActiveRecord::Base.stub(:configurations, @configurations) do
# To refrain from connecting to a newly created empty DB in
# sqlite3_mem tests
ActiveRecord::Base.connection_handler.stub(
:establish_connection,
nil
) do
yield
end
end
end
end end
class DatabaseTasksCreateCurrentTest < ActiveRecord::TestCase class DatabaseTasksCreateCurrentTest < ActiveRecord::TestCase
def setup def setup
@old_configurations = ActiveRecord::Base.configurations
@configurations = { @configurations = {
"development" => { "database" => "dev-db" }, "development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" }, "test" => { "database" => "test-db" },
"production" => { "url" => "prod-db-url" } "production" => { "url" => "abstract://prod-db-url" }
} }
ActiveRecord::Base.configurations = @configurations
end
def teardown
ActiveRecord::Base.configurations = @old_configurations
end end
def test_creates_current_environment_database def test_creates_current_environment_database
with_stubbed_configurations_establish_connection do ActiveRecord::Base.configurations do
assert_called_with( assert_called_with(
ActiveRecord::Tasks::DatabaseTasks, ActiveRecord::Tasks::DatabaseTasks,
:create, :create,
@ -267,7 +264,7 @@ module ActiveRecord
end end
def test_creates_current_environment_database_with_url def test_creates_current_environment_database_with_url
with_stubbed_configurations_establish_connection do ActiveRecord::Base.configurations do
assert_called_with( assert_called_with(
ActiveRecord::Tasks::DatabaseTasks, ActiveRecord::Tasks::DatabaseTasks,
:create, :create,
@ -281,7 +278,7 @@ module ActiveRecord
end end
def test_creates_test_and_development_databases_when_env_was_not_specified def test_creates_test_and_development_databases_when_env_was_not_specified
with_stubbed_configurations_establish_connection do ActiveRecord::Base.configurations do
assert_called_with( assert_called_with(
ActiveRecord::Tasks::DatabaseTasks, ActiveRecord::Tasks::DatabaseTasks,
:create, :create,
@ -301,7 +298,7 @@ module ActiveRecord
old_env = ENV["RAILS_ENV"] old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development" ENV["RAILS_ENV"] = "development"
with_stubbed_configurations_establish_connection do ActiveRecord::Base.configurations do
assert_called_with( assert_called_with(
ActiveRecord::Tasks::DatabaseTasks, ActiveRecord::Tasks::DatabaseTasks,
:create, :create,
@ -328,29 +325,27 @@ module ActiveRecord
end end
end end
end end
private
def with_stubbed_configurations_establish_connection
ActiveRecord::Base.stub(:configurations, @configurations) do
ActiveRecord::Base.stub(:establish_connection, nil) do
yield
end
end
end
end end
class DatabaseTasksCreateCurrentThreeTierTest < ActiveRecord::TestCase class DatabaseTasksCreateCurrentThreeTierTest < ActiveRecord::TestCase
def setup def setup
@old_configurations = ActiveRecord::Base.configurations
@configurations = { @configurations = {
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } }, "development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
"test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } }, "test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
"production" => { "primary" => { "url" => "prod-db-url" }, "secondary" => { "url" => "secondary-prod-db-url" } } "production" => { "primary" => { "url" => "abstract://prod-db-url" }, "secondary" => { "url" => "abstract://secondary-prod-db-url" } }
} }
ActiveRecord::Base.configurations = @configurations
end
def teardown
ActiveRecord::Base.configurations = @old_configurations
end end
def test_creates_current_environment_database def test_creates_current_environment_database
with_stubbed_configurations_establish_connection do ActiveRecord::Base.configurations do
assert_called_with( assert_called_with(
ActiveRecord::Tasks::DatabaseTasks, ActiveRecord::Tasks::DatabaseTasks,
:create, :create,
@ -367,7 +362,7 @@ module ActiveRecord
end end
def test_creates_current_environment_database_with_url def test_creates_current_environment_database_with_url
with_stubbed_configurations_establish_connection do ActiveRecord::Base.configurations do
assert_called_with( assert_called_with(
ActiveRecord::Tasks::DatabaseTasks, ActiveRecord::Tasks::DatabaseTasks,
:create, :create,
@ -384,7 +379,7 @@ module ActiveRecord
end end
def test_creates_test_and_development_databases_when_env_was_not_specified def test_creates_test_and_development_databases_when_env_was_not_specified
with_stubbed_configurations_establish_connection do ActiveRecord::Base.configurations do
assert_called_with( assert_called_with(
ActiveRecord::Tasks::DatabaseTasks, ActiveRecord::Tasks::DatabaseTasks,
:create, :create,
@ -406,7 +401,7 @@ module ActiveRecord
old_env = ENV["RAILS_ENV"] old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development" ENV["RAILS_ENV"] = "development"
with_stubbed_configurations_establish_connection do ActiveRecord::Base.configurations do
assert_called_with( assert_called_with(
ActiveRecord::Tasks::DatabaseTasks, ActiveRecord::Tasks::DatabaseTasks,
:create, :create,
@ -439,16 +434,6 @@ module ActiveRecord
end end
end end
end end
private
def with_stubbed_configurations_establish_connection
ActiveRecord::Base.stub(:configurations, @configurations) do
ActiveRecord::Base.stub(:establish_connection, nil) do
yield
end
end
end
end end
class DatabaseTasksDropTest < ActiveRecord::TestCase class DatabaseTasksDropTest < ActiveRecord::TestCase
@ -467,20 +452,24 @@ module ActiveRecord
class DatabaseTasksDropAllTest < ActiveRecord::TestCase class DatabaseTasksDropAllTest < ActiveRecord::TestCase
def setup def setup
@old_configurations = ActiveRecord::Base.configurations
@configurations = { development: { "database" => "my-db" } } @configurations = { development: { "database" => "my-db" } }
$stdout, @original_stdout = StringIO.new, $stdout $stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr $stderr, @original_stderr = StringIO.new, $stderr
ActiveRecord::Base.configurations = @configurations
end end
def teardown def teardown
$stdout, $stderr = @original_stdout, @original_stderr $stdout, $stderr = @original_stdout, @original_stderr
ActiveRecord::Base.configurations = @old_configurations
end end
def test_ignores_configurations_without_databases def test_ignores_configurations_without_databases
@configurations[:development]["database"] = nil @configurations[:development]["database"] = nil
ActiveRecord::Base.stub(:configurations, @configurations) do ActiveRecord::Base.configurations do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all ActiveRecord::Tasks::DatabaseTasks.drop_all
end end
@ -490,7 +479,7 @@ module ActiveRecord
def test_ignores_remote_databases def test_ignores_remote_databases
@configurations[:development]["host"] = "my.server.tld" @configurations[:development]["host"] = "my.server.tld"
ActiveRecord::Base.stub(:configurations, @configurations) do ActiveRecord::Base.configurations do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all ActiveRecord::Tasks::DatabaseTasks.drop_all
end end
@ -500,7 +489,7 @@ module ActiveRecord
def test_warning_for_remote_databases def test_warning_for_remote_databases
@configurations[:development]["host"] = "my.server.tld" @configurations[:development]["host"] = "my.server.tld"
ActiveRecord::Base.stub(:configurations, @configurations) do ActiveRecord::Base.configurations do
ActiveRecord::Tasks::DatabaseTasks.drop_all ActiveRecord::Tasks::DatabaseTasks.drop_all
assert_match "This task only modifies local databases. my-db is on a remote host.", assert_match "This task only modifies local databases. my-db is on a remote host.",
@ -511,7 +500,7 @@ module ActiveRecord
def test_drops_configurations_with_local_ip def test_drops_configurations_with_local_ip
@configurations[:development]["host"] = "127.0.0.1" @configurations[:development]["host"] = "127.0.0.1"
ActiveRecord::Base.stub(:configurations, @configurations) do ActiveRecord::Base.configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all ActiveRecord::Tasks::DatabaseTasks.drop_all
end end
@ -521,7 +510,7 @@ module ActiveRecord
def test_drops_configurations_with_local_host def test_drops_configurations_with_local_host
@configurations[:development]["host"] = "localhost" @configurations[:development]["host"] = "localhost"
ActiveRecord::Base.stub(:configurations, @configurations) do ActiveRecord::Base.configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all ActiveRecord::Tasks::DatabaseTasks.drop_all
end end
@ -531,7 +520,7 @@ module ActiveRecord
def test_drops_configurations_with_blank_hosts def test_drops_configurations_with_blank_hosts
@configurations[:development]["host"] = nil @configurations[:development]["host"] = nil
ActiveRecord::Base.stub(:configurations, @configurations) do ActiveRecord::Base.configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all ActiveRecord::Tasks::DatabaseTasks.drop_all
end end
@ -541,15 +530,22 @@ module ActiveRecord
class DatabaseTasksDropCurrentTest < ActiveRecord::TestCase class DatabaseTasksDropCurrentTest < ActiveRecord::TestCase
def setup def setup
@old_configurations = ActiveRecord::Base.configurations
@configurations = { @configurations = {
"development" => { "database" => "dev-db" }, "development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" }, "test" => { "database" => "test-db" },
"production" => { "url" => "prod-db-url" } "production" => { "url" => "abstract://prod-db-url" }
} }
ActiveRecord::Base.configurations = @configurations
end
def teardown
ActiveRecord::Base.configurations = @old_configurations
end end
def test_drops_current_environment_database def test_drops_current_environment_database
ActiveRecord::Base.stub(:configurations, @configurations) do ActiveRecord::Base.configurations do
assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop, assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop,
["database" => "test-db"]) do ["database" => "test-db"]) do
ActiveRecord::Tasks::DatabaseTasks.drop_current( ActiveRecord::Tasks::DatabaseTasks.drop_current(
@ -560,7 +556,7 @@ module ActiveRecord
end end
def test_drops_current_environment_database_with_url def test_drops_current_environment_database_with_url
ActiveRecord::Base.stub(:configurations, @configurations) do ActiveRecord::Base.configurations do
assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop, assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop,
["url" => "prod-db-url"]) do ["url" => "prod-db-url"]) do
ActiveRecord::Tasks::DatabaseTasks.drop_current( ActiveRecord::Tasks::DatabaseTasks.drop_current(
@ -571,7 +567,7 @@ module ActiveRecord
end end
def test_drops_test_and_development_databases_when_env_was_not_specified def test_drops_test_and_development_databases_when_env_was_not_specified
ActiveRecord::Base.stub(:configurations, @configurations) do ActiveRecord::Base.configurations do
assert_called_with( assert_called_with(
ActiveRecord::Tasks::DatabaseTasks, ActiveRecord::Tasks::DatabaseTasks,
:drop, :drop,
@ -591,7 +587,7 @@ module ActiveRecord
old_env = ENV["RAILS_ENV"] old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development" ENV["RAILS_ENV"] = "development"
ActiveRecord::Base.stub(:configurations, @configurations) do ActiveRecord::Base.configurations do
assert_called_with( assert_called_with(
ActiveRecord::Tasks::DatabaseTasks, ActiveRecord::Tasks::DatabaseTasks,
:drop, :drop,
@ -612,15 +608,22 @@ module ActiveRecord
class DatabaseTasksDropCurrentThreeTierTest < ActiveRecord::TestCase class DatabaseTasksDropCurrentThreeTierTest < ActiveRecord::TestCase
def setup def setup
@old_configurations = ActiveRecord::Base.configurations
@configurations = { @configurations = {
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } }, "development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
"test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } }, "test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
"production" => { "primary" => { "url" => "prod-db-url" }, "secondary" => { "url" => "secondary-prod-db-url" } } "production" => { "primary" => { "url" => "abstract://prod-db-url" }, "secondary" => { "url" => "abstract://secondary-prod-db-url" } }
} }
ActiveRecord::Base.configurations = @configurations
end
def teardown
ActiveRecord::Base.configurations = @old_configurations
end end
def test_drops_current_environment_database def test_drops_current_environment_database
ActiveRecord::Base.stub(:configurations, @configurations) do ActiveRecord::Base.configurations do
assert_called_with( assert_called_with(
ActiveRecord::Tasks::DatabaseTasks, ActiveRecord::Tasks::DatabaseTasks,
:drop, :drop,
@ -637,7 +640,7 @@ module ActiveRecord
end end
def test_drops_current_environment_database_with_url def test_drops_current_environment_database_with_url
ActiveRecord::Base.stub(:configurations, @configurations) do ActiveRecord::Base.configurations do
assert_called_with( assert_called_with(
ActiveRecord::Tasks::DatabaseTasks, ActiveRecord::Tasks::DatabaseTasks,
:drop, :drop,
@ -654,7 +657,7 @@ module ActiveRecord
end end
def test_drops_test_and_development_databases_when_env_was_not_specified def test_drops_test_and_development_databases_when_env_was_not_specified
ActiveRecord::Base.stub(:configurations, @configurations) do ActiveRecord::Base.configurations do
assert_called_with( assert_called_with(
ActiveRecord::Tasks::DatabaseTasks, ActiveRecord::Tasks::DatabaseTasks,
:drop, :drop,
@ -676,7 +679,7 @@ module ActiveRecord
old_env = ENV["RAILS_ENV"] old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development" ENV["RAILS_ENV"] = "development"
ActiveRecord::Base.stub(:configurations, @configurations) do ActiveRecord::Base.configurations do
assert_called_with( assert_called_with(
ActiveRecord::Tasks::DatabaseTasks, ActiveRecord::Tasks::DatabaseTasks,
:drop, :drop,
@ -848,12 +851,16 @@ module ActiveRecord
class DatabaseTasksPurgeCurrentTest < ActiveRecord::TestCase class DatabaseTasksPurgeCurrentTest < ActiveRecord::TestCase
def test_purges_current_environment_database def test_purges_current_environment_database
old_configurations = ActiveRecord::Base.configurations
configurations = { configurations = {
"development" => { "database" => "dev-db" }, "development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" }, "test" => { "database" => "test-db" },
"production" => { "database" => "prod-db" } "production" => { "database" => "prod-db" }
} }
ActiveRecord::Base.stub(:configurations, configurations) do
ActiveRecord::Base.configurations = configurations
ActiveRecord::Base.configurations do
assert_called_with( assert_called_with(
ActiveRecord::Tasks::DatabaseTasks, ActiveRecord::Tasks::DatabaseTasks,
:purge, :purge,
@ -864,13 +871,17 @@ module ActiveRecord
end end
end end
end end
ensure
ActiveRecord::Base.configurations = old_configurations
end end
end end
class DatabaseTasksPurgeAllTest < ActiveRecord::TestCase class DatabaseTasksPurgeAllTest < ActiveRecord::TestCase
def test_purge_all_local_configurations def test_purge_all_local_configurations
old_configurations = ActiveRecord::Base.configurations
configurations = { development: { "database" => "my-db" } } configurations = { development: { "database" => "my-db" } }
ActiveRecord::Base.stub(:configurations, configurations) do ActiveRecord::Base.configurations = configurations
ActiveRecord::Base.configurations do
assert_called_with( assert_called_with(
ActiveRecord::Tasks::DatabaseTasks, ActiveRecord::Tasks::DatabaseTasks,
:purge, :purge,
@ -879,6 +890,8 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.purge_all ActiveRecord::Tasks::DatabaseTasks.purge_all
end end
end end
ensure
ActiveRecord::Base.configurations = old_configurations
end end
end end

View File

@ -0,0 +1,591 @@
# frozen_string_literal: true
require "cases/helper"
require "active_record/tasks/database_tasks"
module ActiveRecord
class LegacyDatabaseTasksCreateAllTest < ActiveRecord::TestCase
def setup
@old_configurations = ActiveRecord::Base.configurations.to_h
@configurations = { "development" => { "database" => "my-db" } }
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
ActiveRecord::Base.configurations = @configurations
end
def teardown
$stdout, $stderr = @original_stdout, @original_stderr
ActiveRecord::Base.configurations = @old_configurations
end
def test_ignores_configurations_without_databases
@configurations["development"]["database"] = nil
ActiveRecord::Base.configurations.to_h do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
ActiveRecord::Tasks::DatabaseTasks.create_all
end
end
end
def test_ignores_remote_databases
@configurations["development"]["host"] = "my.server.tld"
ActiveRecord::Base.configurations.to_h do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
ActiveRecord::Tasks::DatabaseTasks.create_all
end
end
end
def test_warning_for_remote_databases
@configurations["development"]["host"] = "my.server.tld"
ActiveRecord::Base.configurations.to_h do
ActiveRecord::Tasks::DatabaseTasks.create_all
assert_match "This task only modifies local databases. my-db is on a remote host.",
$stderr.string
end
end
def test_creates_configurations_with_local_ip
@configurations["development"]["host"] = "127.0.0.1"
ActiveRecord::Base.configurations.to_h do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
ActiveRecord::Tasks::DatabaseTasks.create_all
end
end
end
def test_creates_configurations_with_local_host
@configurations["development"]["host"] = "localhost"
ActiveRecord::Base.configurations.to_h do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
ActiveRecord::Tasks::DatabaseTasks.create_all
end
end
end
def test_creates_configurations_with_blank_hosts
@configurations["development"]["host"] = nil
ActiveRecord::Base.configurations.to_h do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
ActiveRecord::Tasks::DatabaseTasks.create_all
end
end
end
end
class LegacyDatabaseTasksCreateCurrentTest < ActiveRecord::TestCase
def setup
@old_configurations = ActiveRecord::Base.configurations.to_h
@configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
"production" => { "url" => "abstract://prod-db-url" }
}
ActiveRecord::Base.configurations = @configurations
end
def teardown
ActiveRecord::Base.configurations = @old_configurations
end
def test_creates_current_environment_database
ActiveRecord::Base.configurations.to_h do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
["database" => "test-db"],
) do
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new("test")
)
end
end
end
def test_creates_current_environment_database_with_url
ActiveRecord::Base.configurations.to_h do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
["url" => "prod-db-url"],
) do
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new("production")
)
end
end
end
def test_creates_test_and_development_databases_when_env_was_not_specified
ActiveRecord::Base.configurations.to_h do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
[
["database" => "dev-db"],
["database" => "test-db"]
],
) do
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new("development")
)
end
end
end
def test_creates_test_and_development_databases_when_rails_env_is_development
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
ActiveRecord::Base.configurations.to_h do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
[
["database" => "dev-db"],
["database" => "test-db"]
],
) do
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new("development")
)
end
end
ensure
ENV["RAILS_ENV"] = old_env
end
def test_establishes_connection_for_the_given_environments
ActiveRecord::Tasks::DatabaseTasks.stub(:create, nil) do
assert_called_with(ActiveRecord::Base, :establish_connection, [:development]) do
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new("development")
)
end
end
end
end
class LegacyDatabaseTasksCreateCurrentThreeTierTest < ActiveRecord::TestCase
def setup
@old_configurations = ActiveRecord::Base.configurations.to_h
@configurations = {
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
"test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
"production" => { "primary" => { "url" => "abstract://prod-db-url" }, "secondary" => { "url" => "abstract://secondary-prod-db-url" } }
}
ActiveRecord::Base.configurations = @configurations
end
def teardown
ActiveRecord::Base.configurations = @old_configurations
end
def test_creates_current_environment_database
ActiveRecord::Base.configurations.to_h do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
[
["database" => "test-db"],
["database" => "secondary-test-db"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new("test")
)
end
end
end
def test_creates_current_environment_database_with_url
ActiveRecord::Base.configurations.to_h do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
[
["url" => "prod-db-url"],
["url" => "secondary-prod-db-url"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new("production")
)
end
end
end
def test_creates_test_and_development_databases_when_env_was_not_specified
ActiveRecord::Base.configurations.to_h do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
[
["database" => "dev-db"],
["database" => "secondary-dev-db"],
["database" => "test-db"],
["database" => "secondary-test-db"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new("development")
)
end
end
end
def test_creates_test_and_development_databases_when_rails_env_is_development
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
ActiveRecord::Base.configurations.to_h do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
[
["database" => "dev-db"],
["database" => "secondary-dev-db"],
["database" => "test-db"],
["database" => "secondary-test-db"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new("development")
)
end
end
ensure
ENV["RAILS_ENV"] = old_env
end
def test_establishes_connection_for_the_given_environments_config
ActiveRecord::Tasks::DatabaseTasks.stub(:create, nil) do
assert_called_with(
ActiveRecord::Base,
:establish_connection,
[:development]
) do
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new("development")
)
end
end
end
end
class LegacyDatabaseTasksDropAllTest < ActiveRecord::TestCase
def setup
@old_configurations = ActiveRecord::Base.configurations.to_h
@configurations = { development: { "database" => "my-db" } }
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
ActiveRecord::Base.configurations = @configurations
end
def teardown
$stdout, $stderr = @original_stdout, @original_stderr
ActiveRecord::Base.configurations = @old_configurations
end
def test_ignores_configurations_without_databases
@configurations[:development]["database"] = nil
ActiveRecord::Base.configurations.to_h do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
end
end
def test_ignores_remote_databases
@configurations[:development]["host"] = "my.server.tld"
ActiveRecord::Base.configurations.to_h do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
end
end
def test_warning_for_remote_databases
@configurations[:development]["host"] = "my.server.tld"
ActiveRecord::Base.configurations.to_h do
ActiveRecord::Tasks::DatabaseTasks.drop_all
assert_match "This task only modifies local databases. my-db is on a remote host.",
$stderr.string
end
end
def test_drops_configurations_with_local_ip
@configurations[:development]["host"] = "127.0.0.1"
ActiveRecord::Base.configurations.to_h do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
end
end
def test_drops_configurations_with_local_host
@configurations[:development]["host"] = "localhost"
ActiveRecord::Base.configurations.to_h do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
end
end
def test_drops_configurations_with_blank_hosts
@configurations[:development]["host"] = nil
ActiveRecord::Base.configurations.to_h do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
end
end
end
class LegacyDatabaseTasksDropCurrentTest < ActiveRecord::TestCase
def setup
@old_configurations = ActiveRecord::Base.configurations.to_h
@configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
"production" => { "url" => "abstract://prod-db-url" }
}
ActiveRecord::Base.configurations = @configurations
end
def teardown
ActiveRecord::Base.configurations = @old_configurations
end
def test_drops_current_environment_database
ActiveRecord::Base.configurations.to_h do
assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop,
["database" => "test-db"]) do
ActiveRecord::Tasks::DatabaseTasks.drop_current(
ActiveSupport::StringInquirer.new("test")
)
end
end
end
def test_drops_current_environment_database_with_url
ActiveRecord::Base.configurations.to_h do
assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop,
["url" => "prod-db-url"]) do
ActiveRecord::Tasks::DatabaseTasks.drop_current(
ActiveSupport::StringInquirer.new("production")
)
end
end
end
def test_drops_test_and_development_databases_when_env_was_not_specified
ActiveRecord::Base.configurations.to_h do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
[
["database" => "dev-db"],
["database" => "test-db"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.drop_current(
ActiveSupport::StringInquirer.new("development")
)
end
end
end
def test_drops_testand_development_databases_when_rails_env_is_development
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
ActiveRecord::Base.configurations.to_h do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
[
["database" => "dev-db"],
["database" => "test-db"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.drop_current(
ActiveSupport::StringInquirer.new("development")
)
end
end
ensure
ENV["RAILS_ENV"] = old_env
end
end
class LegacyDatabaseTasksDropCurrentThreeTierTest < ActiveRecord::TestCase
def setup
@old_configurations = ActiveRecord::Base.configurations.to_h
@configurations = {
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
"test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
"production" => { "primary" => { "url" => "abstract://prod-db-url" }, "secondary" => { "url" => "abstract://secondary-prod-db-url" } }
}
ActiveRecord::Base.configurations = @configurations
end
def teardown
ActiveRecord::Base.configurations = @old_configurations
end
def test_drops_current_environment_database
ActiveRecord::Base.configurations.to_h do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
[
["database" => "test-db"],
["database" => "secondary-test-db"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.drop_current(
ActiveSupport::StringInquirer.new("test")
)
end
end
end
def test_drops_current_environment_database_with_url
ActiveRecord::Base.configurations.to_h do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
[
["url" => "prod-db-url"],
["url" => "secondary-prod-db-url"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.drop_current(
ActiveSupport::StringInquirer.new("production")
)
end
end
end
def test_drops_test_and_development_databases_when_env_was_not_specified
ActiveRecord::Base.configurations.to_h do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
[
["database" => "dev-db"],
["database" => "secondary-dev-db"],
["database" => "test-db"],
["database" => "secondary-test-db"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.drop_current(
ActiveSupport::StringInquirer.new("development")
)
end
end
end
def test_drops_testand_development_databases_when_rails_env_is_development
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
ActiveRecord::Base.configurations.to_h do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
[
["database" => "dev-db"],
["database" => "secondary-dev-db"],
["database" => "test-db"],
["database" => "secondary-test-db"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.drop_current(
ActiveSupport::StringInquirer.new("development")
)
end
end
ensure
ENV["RAILS_ENV"] = old_env
end
end
class LegacyDatabaseTasksPurgeCurrentTest < ActiveRecord::TestCase
def test_purges_current_environment_database
@old_configurations = ActiveRecord::Base.configurations.to_h
configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
"production" => { "database" => "prod-db" }
}
ActiveRecord::Base.configurations = configurations
ActiveRecord::Base.configurations.to_h do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:purge,
["database" => "prod-db"]
) do
assert_called_with(ActiveRecord::Base, :establish_connection, [:production]) do
ActiveRecord::Tasks::DatabaseTasks.purge_current("production")
end
end
end
ensure
ActiveRecord::Base.configurations = @old_configurations
end
end
class LegacyDatabaseTasksPurgeAllTest < ActiveRecord::TestCase
def test_purge_all_local_configurations
@old_configurations = ActiveRecord::Base.configurations.to_h
configurations = { development: { "database" => "my-db" } }
ActiveRecord::Base.configurations = configurations
ActiveRecord::Base.configurations.to_h do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:purge,
["database" => "my-db"]
) do
ActiveRecord::Tasks::DatabaseTasks.purge_all
end
end
ensure
ActiveRecord::Base.configurations = @old_configurations
end
end
end

View File

@ -908,7 +908,15 @@ $ echo $DATABASE_URL
postgresql://localhost/my_database postgresql://localhost/my_database
$ rails runner 'puts ActiveRecord::Base.configurations' $ rails runner 'puts ActiveRecord::Base.configurations'
{"development"=>{"adapter"=>"postgresql", "host"=>"localhost", "database"=>"my_database"}} #<ActiveRecord::DatabaseConfigurations:0x00007fd50e209a28>
$ rails runner 'puts ActiveRecord::Base.configurations.inspect'
#<ActiveRecord::DatabaseConfigurations:0x00007fc8eab02880 @configurations=[
#<ActiveRecord::DatabaseConfigurations::UrlConfig:0x00007fc8eab020b0
@env_name="development", @spec_name="primary",
@config={"adapter"=>"postgresql", "database"=>"my_database", "host"=>"localhost"}
@url="postgresql://localhost/my_database">
]
``` ```
Here the adapter, host, and database match the information in `ENV['DATABASE_URL']`. Here the adapter, host, and database match the information in `ENV['DATABASE_URL']`.
@ -925,7 +933,15 @@ $ echo $DATABASE_URL
postgresql://localhost/my_database postgresql://localhost/my_database
$ rails runner 'puts ActiveRecord::Base.configurations' $ rails runner 'puts ActiveRecord::Base.configurations'
{"development"=>{"adapter"=>"postgresql", "host"=>"localhost", "database"=>"my_database", "pool"=>5}} #<ActiveRecord::DatabaseConfigurations:0x00007fd50e209a28>
$ rails runner 'puts ActiveRecord::Base.configurations.inspect'
#<ActiveRecord::DatabaseConfigurations:0x00007fc8eab02880 @configurations=[
#<ActiveRecord::DatabaseConfigurations::UrlConfig:0x00007fc8eab020b0
@env_name="development", @spec_name="primary",
@config={"adapter"=>"postgresql", "database"=>"my_database", "host"=>"localhost", "pool"=>5}
@url="postgresql://localhost/my_database">
]
``` ```
Since pool is not in the `ENV['DATABASE_URL']` provided connection information its information is merged in. Since `adapter` is duplicate, the `ENV['DATABASE_URL']` connection information wins. Since pool is not in the `ENV['DATABASE_URL']` provided connection information its information is merged in. Since `adapter` is duplicate, the `ENV['DATABASE_URL']` connection information wins.
@ -935,13 +951,21 @@ The only way to explicitly not use the connection information in `ENV['DATABASE_
``` ```
$ cat config/database.yml $ cat config/database.yml
development: development:
url: sqlite3:NOT_my_database url: sqlite3://NOT_my_database
$ echo $DATABASE_URL $ echo $DATABASE_URL
postgresql://localhost/my_database postgresql://localhost/my_database
$ rails runner 'puts ActiveRecord::Base.configurations' $ rails runner 'puts ActiveRecord::Base.configurations'
{"development"=>{"adapter"=>"sqlite3", "database"=>"NOT_my_database"}} #<ActiveRecord::DatabaseConfigurations:0x00007fd50e209a28>
$ rails runner 'puts ActiveRecord::Base.configurations.inspect'
#<ActiveRecord::DatabaseConfigurations:0x00007fc8eab02880 @configurations=[
#<ActiveRecord::DatabaseConfigurations::UrlConfig:0x00007fc8eab020b0
@env_name="development", @spec_name="primary",
@config={"adapter"=>"sqlite3", "database"=>"NOT_my_database", "host"=>"localhost"}
@url="sqlite3://NOT_my_database">
]
``` ```
Here the connection information in `ENV['DATABASE_URL']` is ignored, note the different adapter and database name. Here the connection information in `ENV['DATABASE_URL']` is ignored, note the different adapter and database name.

View File

@ -127,36 +127,36 @@ EOS
test "db:create and db:drop works on all databases for env" do test "db:create and db:drop works on all databases for env" do
require "#{app_path}/config/environment" require "#{app_path}/config/environment"
ActiveRecord::Base.configurations[Rails.env].each do |namespace, config| ActiveRecord::Base.configurations.configs_for(Rails.env).each do |db_config|
db_create_and_drop namespace, config["database"] db_create_and_drop db_config.spec_name, db_config.config["database"]
end end
end end
test "db:create:namespace and db:drop:namespace works on specified databases" do test "db:create:namespace and db:drop:namespace works on specified databases" do
require "#{app_path}/config/environment" require "#{app_path}/config/environment"
ActiveRecord::Base.configurations[Rails.env].each do |namespace, config| ActiveRecord::Base.configurations.configs_for(Rails.env).each do |db_config|
db_create_and_drop_namespace namespace, config["database"] db_create_and_drop_namespace db_config.spec_name, db_config.config["database"]
end end
end end
test "db:migrate and db:schema:dump and db:schema:load works on all databases" do test "db:migrate and db:schema:dump and db:schema:load works on all databases" do
require "#{app_path}/config/environment" require "#{app_path}/config/environment"
ActiveRecord::Base.configurations[Rails.env].each do |namespace, config| ActiveRecord::Base.configurations.configs_for(Rails.env).each do |db_config|
db_migrate_and_schema_dump_and_load namespace, config["database"], "schema" db_migrate_and_schema_dump_and_load db_config.spec_name, db_config.config["database"], "schema"
end end
end end
test "db:migrate and db:structure:dump and db:structure:load works on all databases" do test "db:migrate and db:structure:dump and db:structure:load works on all databases" do
require "#{app_path}/config/environment" require "#{app_path}/config/environment"
ActiveRecord::Base.configurations[Rails.env].each do |namespace, config| ActiveRecord::Base.configurations.configs_for(Rails.env).each do |db_config|
db_migrate_and_schema_dump_and_load namespace, config["database"], "structure" db_migrate_and_schema_dump_and_load db_config.spec_name, db_config.config["database"], "structure"
end end
end end
test "db:migrate:namespace works" do test "db:migrate:namespace works" do
require "#{app_path}/config/environment" require "#{app_path}/config/environment"
ActiveRecord::Base.configurations[Rails.env].each do |namespace, config| ActiveRecord::Base.configurations.configs_for(Rails.env).each do |db_config|
db_migrate_namespaced namespace, config["database"] db_migrate_namespaced db_config.spec_name, db_config.config["database"]
end end
end end
end end