aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/connection_adapters/connection_specification.rb
diff options
context:
space:
mode:
authorAaron Patterson <aaron.patterson@gmail.com>2014-01-10 11:00:30 -0800
committerAaron Patterson <aaron.patterson@gmail.com>2014-01-10 11:00:30 -0800
commitcaa981d88112f019ade868f75af6b5f399c244a4 (patch)
treeb760cd5a28612eae612f6055680083cff206b043 /activerecord/lib/active_record/connection_adapters/connection_specification.rb
parent71d19f15171e59b5dfddd66f3fa3a234c3f7911d (diff)
parentbd1bf2c5b8480fae0379890347d58b9fe8231e3e (diff)
downloadrails-caa981d88112f019ade868f75af6b5f399c244a4.tar.gz
rails-caa981d88112f019ade868f75af6b5f399c244a4.tar.bz2
rails-caa981d88112f019ade868f75af6b5f399c244a4.zip
Merge branch 'master' into laurocaetano-fix_send_file
* master: (536 commits) doc, API example on how to use `Model#exists?` with multiple IDs. [ci skip] Restore DATABASE_URL even if it's nil in connection_handler test [ci skip] - error_messages_for has been deprecated since 2.3.8 - lets reduce any confusion for users Ensure Active Record connection consistency Revert "ask the fixture set for the sql statements" Check `respond_to` before delegation due to: https://github.com/ruby/ruby/commit/d781caaf313b8649948c107bba277e5ad7307314 Adding Hash#compact and Hash#compact! methods MySQL version 4.1 was EOL on December 31, 2009 We should at least recommend modern versions of MySQL to users. clear cache on body close so that cache remains during rendering add a more restricted codepath for templates fixes #13390 refactor generator tests to use block form of Tempfile Fix typo [ci skip] Move finish_template as the last public method in the generator Minor typos fix [ci skip] make `change_column_null` reversible. Closes #13576. create/drop test and development databases only if RAILS_ENV is nil Revert "Speedup String#to" typo fix in test name. [ci skip]. `core_ext/string/access.rb` test what we are documenting. Fix typo in image_tag documentation ... Conflicts: actionpack/CHANGELOG.md
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters/connection_specification.rb')
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb243
1 files changed, 200 insertions, 43 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 64fc9e95d8..3f8b14bf67 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -13,41 +13,146 @@ module ActiveRecord
@config = original.config.dup
end
+ # Expands a connection string into a hash.
+ class ConnectionUrlResolver # :nodoc:
+
+ # == Example
+ #
+ # url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000"
+ # ConnectionUrlResolver.new(url).to_hash
+ # # => {
+ # "adapter" => "postgresql",
+ # "host" => "localhost",
+ # "port" => 9000,
+ # "database" => "foo_test",
+ # "username" => "foo",
+ # "password" => "bar",
+ # "pool" => "5",
+ # "timeout" => "3000"
+ # }
+ def initialize(url)
+ raise "Database URL cannot be empty" if url.blank?
+ @uri = URI.parse(url)
+ @adapter = @uri.scheme
+ @adapter = "postgresql" if @adapter == "postgres"
+ @query = @uri.query || ''
+ end
+
+ # Converts the given URL to a full connection hash.
+ def to_hash
+ config = raw_config.reject { |_,value| value.blank? }
+ config.map { |key,value| config[key] = uri_parser.unescape(value) if value.is_a? String }
+ config
+ end
+
+ private
+
+ def uri
+ @uri
+ end
+
+ def uri_parser
+ @uri_parser ||= URI::Parser.new
+ end
+
+ # Converts the query parameters of the URI into a hash.
+ #
+ # "localhost?pool=5&reap_frequency=2"
+ # # => { "pool" => "5", "reap_frequency" => "2" }
+ #
+ # returns empty hash if no query present.
+ #
+ # "localhost"
+ # # => {}
+ def query_hash
+ Hash[@query.split("&").map { |pair| pair.split("=") }]
+ end
+
+ def raw_config
+ query_hash.merge({
+ "adapter" => @adapter,
+ "username" => uri.user,
+ "password" => uri.password,
+ "port" => uri.port,
+ "database" => database,
+ "host" => uri.host })
+ end
+
+ # Returns name of the database.
+ # Sqlite3 expects this to be a full path or `:memory:`.
+ def database
+ if @adapter == 'sqlite3'
+ if '/:memory:' == uri.path
+ ':memory:'
+ else
+ uri.path
+ end
+ else
+ uri.path.sub(%r{^/},"")
+ end
+ end
+ end
+
##
- # Builds a ConnectionSpecification from user input
+ # Builds a ConnectionSpecification from user input.
class Resolver # :nodoc:
- attr_reader :config, :klass, :configurations
+ attr_reader :configurations
- def initialize(config, configurations)
- @config = config
+ # Accepts a hash two layers deep, keys on the first layer represent
+ # environments such as "production". Keys must be strings.
+ def initialize(configurations)
@configurations = configurations
end
- def spec
- case config
- when nil
- raise AdapterNotSpecified unless defined?(Rails.env)
- resolve_string_connection Rails.env
- when Symbol, String
- resolve_string_connection config.to_s
- when Hash
- resolve_hash_connection config
+ # Returns a hash with database connection information.
+ #
+ # == Examples
+ #
+ # Full hash Configuration.
+ #
+ # configurations = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
+ # Resolver.new(configurations).resolve(:production)
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3"}
+ #
+ # Initialized with URL configuration strings.
+ #
+ # configurations = { "production" => "postgresql://localhost/foo" }
+ # Resolver.new(configurations).resolve(:production)
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
+ #
+ def resolve(config)
+ if config
+ resolve_connection config
+ elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call
+ resolve_env_connection env.to_sym
+ else
+ raise AdapterNotSpecified
end
end
- private
- def resolve_string_connection(spec) # :nodoc:
- hash = configurations.fetch(spec) do |k|
- connection_url_to_hash(k)
+ # Expands each key in @configurations hash into fully resolved hash
+ def resolve_all
+ config = configurations.dup
+ config.each do |key, value|
+ config[key] = resolve(value) if value
end
-
- raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash
-
- resolve_hash_connection hash
+ config
end
- def resolve_hash_connection(spec) # :nodoc:
- spec = spec.symbolize_keys
+ # Returns an instance of ConnectionSpecification for a given adapter.
+ # Accepts a hash one layer deep that contains all connection information.
+ #
+ # == Example
+ #
+ # config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
+ # spec = Resolver.new(config).spec(:production)
+ # spec.adapter_method
+ # # => "sqlite3"
+ # spec.config
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" }
+ #
+ def spec(config)
+ spec = resolve(config).symbolize_keys
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
@@ -61,35 +166,87 @@ module ActiveRecord
end
adapter_method = "#{spec[:adapter]}_connection"
-
ConnectionSpecification.new(spec, adapter_method)
end
- def connection_url_to_hash(url) # :nodoc:
- config = URI.parse url
- adapter = config.scheme
- adapter = "postgresql" if adapter == "postgres"
- spec = { :adapter => adapter,
- :username => config.user,
- :password => config.password,
- :port => config.port,
- :database => config.path.sub(%r{^/},""),
- :host => config.host }
-
- spec.reject!{ |_,value| value.blank? }
-
- uri_parser = URI::Parser.new
-
- spec.map { |key,value| spec[key] = uri_parser.unescape(value) if value.is_a?(String) }
+ private
- if config.query
- options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
+ # Returns fully resolved connection, accepts hash, string or symbol.
+ # Always returns a hash.
+ #
+ # == Examples
+ #
+ # Symbol representing current environment.
+ #
+ # Resolver.new("production" => {}).resolve_connection(:production)
+ # # => {}
+ #
+ # One layer deep hash of connection values.
+ #
+ # Resolver.new({}).resolve_connection("adapter" => "sqlite3")
+ # # => { "adapter" => "sqlite3" }
+ #
+ # Connection URL.
+ #
+ # Resolver.new({}).resolve_connection("postgresql://localhost/foo")
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
+ #
+ def resolve_connection(spec)
+ case spec
+ when Symbol, String
+ resolve_env_connection spec
+ when Hash
+ resolve_hash_connection spec
+ end
+ end
- spec.merge!(options)
+ # Takes the environment such as `:production` or `:development`.
+ # This requires that the @configurations was initialized with a key that
+ # matches.
+ #
+ #
+ # Resolver.new("production" => {}).resolve_env_connection(:production)
+ # # => {}
+ #
+ # Takes a connection URL.
+ #
+ # Resolver.new({}).resolve_env_connection("postgresql://localhost/foo")
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
+ #
+ def resolve_env_connection(spec)
+ # Rails has historically accepted a string to mean either
+ # an environment key or a URL spec, so we have deprecated
+ # this ambiguous behaviour and in the future this function
+ # can be removed in favor of resolve_string_connection and
+ # resolve_symbol_connection.
+ if config = configurations[spec.to_s]
+ if spec.is_a?(String)
+ ActiveSupport::Deprecation.warn "Passing a string to ActiveRecord::Base.establish_connection " \
+ "for a configuration lookup is deprecated, please pass a symbol (#{spec.to_sym.inspect}) instead"
+ end
+ resolve_connection(config)
+ elsif spec.is_a?(String)
+ resolve_string_connection(spec)
+ else
+ raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available configuration: #{configurations.inspect}")
end
+ end
+ # Accepts a hash. Expands the "url" key that contains a
+ # URL database connection to a full connection
+ # hash and merges with the rest of the hash.
+ # Connection details inside of the "url" key win any merge conflicts
+ def resolve_hash_connection(spec)
+ if url = spec.delete("url")
+ connection_hash = resolve_string_connection(url)
+ spec.merge!(connection_hash)
+ end
spec
end
+
+ def resolve_string_connection(url)
+ ConnectionUrlResolver.new(url).to_hash
+ end
end
end
end