Module: ActiveRecord::ConnectionHandling

Included in:
Base
Defined in:
lib/active_record/connection_adapters/mysql2_adapter.rb,
lib/active_record/connection_handling.rb,
lib/active_record/connection_adapters/sqlite3_adapter.rb,
lib/active_record/connection_adapters/postgresql_adapter.rb
more...

Overview

:nodoc:

Constant Summary collapse

RAILS_ENV =
-> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence }
DEFAULT_ENV =
-> { RAILS_ENV.call || "default_env" }
ER_BAD_DB_ERROR =
1049

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#connection_specification_nameObject

Return the specification name from the current class or its parent.


195
196
197
198
199
200
# File 'lib/active_record/connection_handling.rb', line 195

def connection_specification_name
  if !defined?(@connection_specification_name) || @connection_specification_name.nil?
    return self == Base ? "primary" : superclass.connection_specification_name
  end
  @connection_specification_name
end

Instance Method Details

#clear_cache!Object

:nodoc:

[View source]

241
242
243
# File 'lib/active_record/connection_handling.rb', line 241

def clear_cache! # :nodoc:
  connection.schema_cache.clear!
end

#clear_query_caches_for_current_threadObject

Clears the query cache for all connections associated with the current thread.

[View source]

177
178
179
180
181
182
183
# File 'lib/active_record/connection_handling.rb', line 177

def clear_query_caches_for_current_thread
  ActiveRecord::Base.connection_handlers.each_value do |handler|
    handler.connection_pool_list.each do |pool|
      pool.connection.clear_query_cache if pool.active_connection?
    end
  end
end

#connected?Boolean

Returns true if Active Record is connected.

Returns:

  • (Boolean)
[View source]

225
226
227
# File 'lib/active_record/connection_handling.rb', line 225

def connected?
  connection_handler.connected?(connection_specification_name)
end

#connected_to(database: nil, role: nil, prevent_writes: false, &blk) ⇒ Object

Connects to a database or role (ex writing, reading, or another custom role) for the duration of the block.

If a role is passed, Active Record will look up the connection based on the requested role:

ActiveRecord::Base.connected_to(role: :writing) do
  Dog.create! # creates dog using dog writing connection
end

ActiveRecord::Base.connected_to(role: :reading) do
  Dog.create! # throws exception because we're on a replica
end

ActiveRecord::Base.connected_to(role: :unknown_role) do
  # raises exception due to non-existent role
end

The `database` kwarg is deprecated in 6.1 and will be removed in 6.2

It is not recommended for use as it re-establishes a connection every time it is called.

[View source]

103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/active_record/connection_handling.rb', line 103

def connected_to(database: nil, role: nil, prevent_writes: false, &blk)
  if database && role
    raise ArgumentError, "connected_to can only accept a `database` or a `role` argument, but not both arguments."
  elsif database
    if database.is_a?(Hash)
      role, database = database.first
      role = role.to_sym
    end

    config_hash = resolve_config_for_connection(database)
    handler = lookup_connection_handler(role)

    handler.establish_connection(config_hash)

    with_handler(role, &blk)
  elsif role
    prevent_writes = true if role == reading_role

    with_handler(role.to_sym) do
      connection_handler.while_preventing_writes(prevent_writes, &blk)
    end
  else
    raise ArgumentError, "must provide a `database` or a `role`."
  end
end

#connected_to?(role:) ⇒ Boolean

Returns true if role is the current connected role.

ActiveRecord::Base.connected_to(role: :writing) do
  ActiveRecord::Base.connected_to?(role: :writing) #=> true
  ActiveRecord::Base.connected_to?(role: :reading) #=> false
end

Returns:

  • (Boolean)
[View source]

135
136
137
# File 'lib/active_record/connection_handling.rb', line 135

def connected_to?(role:)
  current_role == role.to_sym
end

#connectionObject

Returns the connection currently associated with the class. This can also be used to “borrow” the connection to do database work unrelated to any of the specific Active Records.

[View source]

188
189
190
# File 'lib/active_record/connection_handling.rb', line 188

def connection
  retrieve_connection
end

#connection_configObject

Returns the configuration of the associated connection as a hash:

ActiveRecord::Base.connection_config
# => {pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"}

Please use only for reading.

[View source]

212
213
214
# File 'lib/active_record/connection_handling.rb', line 212

def connection_config
  connection_pool.spec.config
end

#connection_poolObject

[View source]

216
217
218
# File 'lib/active_record/connection_handling.rb', line 216

def connection_pool
  connection_handler.retrieve_connection_pool(connection_specification_name) || raise(ConnectionNotEstablished)
end

#connects_to(database: {}) ⇒ Object

Connects a model to the databases specified. The database keyword takes a hash consisting of a role and a database_key.

This will create a connection handler for switching between connections, look up the config hash using the database_key and finally establishes a connection to that config.

class AnimalsModel < ApplicationRecord
  self.abstract_class = true

  connects_to database: { writing: :primary, reading: :primary_replica }
end

Returns an array of established connections.

[View source]

68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/active_record/connection_handling.rb', line 68

def connects_to(database: {})
  connections = []

  database.each do |role, database_key|
    config_hash = resolve_config_for_connection(database_key)
    handler = lookup_connection_handler(role.to_sym)

    connections << handler.establish_connection(config_hash)
  end

  connections
end

#current_roleObject

Returns the symbol representing the current connected role.

ActiveRecord::Base.connected_to(role: :writing) do
  ActiveRecord::Base.current_role #=> :writing
end

ActiveRecord::Base.connected_to(role: :reading) do
  ActiveRecord::Base.current_role #=> :reading
end
[View source]

148
149
150
# File 'lib/active_record/connection_handling.rb', line 148

def current_role
  connection_handlers.key(connection_handler)
end

#establish_connection(config_or_env = nil) ⇒ Object

Establishes the connection to the database. Accepts a hash as input where the :adapter key must be specified with the name of a database adapter (in lower-case) example for regular databases (MySQL, PostgreSQL, etc):

ActiveRecord::Base.establish_connection(
  adapter:  "mysql2",
  host:     "localhost",
  username: "myuser",
  password: "mypass",
  database: "somedatabase"
)

Example for SQLite database:

ActiveRecord::Base.establish_connection(
  adapter:  "sqlite3",
  database: "path/to/dbfile"
)

Also accepts keys as strings (for parsing from YAML for example):

ActiveRecord::Base.establish_connection(
  "adapter"  => "sqlite3",
  "database" => "path/to/dbfile"
)

Or a URL:

ActiveRecord::Base.establish_connection(
  "postgres://myuser:mypass@localhost/somedatabase"
)

In case ActiveRecord::Base.configurations is set (Rails automatically loads the contents of config/database.yml into it), a symbol can also be given as argument, representing a key in the configuration hash:

ActiveRecord::Base.establish_connection(:production)

The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError may be returned on an error.

[View source]

49
50
51
52
# File 'lib/active_record/connection_handling.rb', line 49

def establish_connection(config_or_env = nil)
  config_hash = resolve_config_for_connection(config_or_env)
  connection_handler.establish_connection(config_hash)
end

#lookup_connection_handler(handler_key) ⇒ Object

:nodoc:

[View source]

152
153
154
155
# File 'lib/active_record/connection_handling.rb', line 152

def lookup_connection_handler(handler_key) # :nodoc:
  handler_key ||= ActiveRecord::Base.writing_role
  connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
end

#mysql2_connection(config) ⇒ Object

Establishes a connection to the database that's used by all Active Record objects.

[View source]

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/active_record/connection_adapters/mysql2_adapter.rb', line 14

def mysql2_connection(config)
  config = config.symbolize_keys
  config[:flags] ||= 0

  if config[:flags].kind_of? Array
    config[:flags].push "FOUND_ROWS"
  else
    config[:flags] |= Mysql2::Client::FOUND_ROWS
  end

  client = Mysql2::Client.new(config)
  ConnectionAdapters::Mysql2Adapter.new(client, logger, nil, config)
rescue Mysql2::Error => error
  if error.error_number == ER_BAD_DB_ERROR
    raise ActiveRecord::NoDatabaseError
  else
    raise
  end
end

#postgresql_connection(config) ⇒ Object

Establishes a connection to the database that's used by all Active Record objects

[View source]

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/active_record/connection_adapters/postgresql_adapter.rb', line 33

def postgresql_connection(config)
  conn_params = config.symbolize_keys

  conn_params.delete_if { |_, v| v.nil? }

  # Map ActiveRecords param names to PGs.
  conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
  conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]

  # Forward only valid config params to PG::Connection.connect.
  valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
  conn_params.slice!(*valid_conn_param_keys)

  conn = PG.connect(conn_params)
  ConnectionAdapters::PostgreSQLAdapter.new(conn, logger, conn_params, config)
rescue ::PG::Error => error
  if error.message.include?(conn_params[:dbname])
    raise ActiveRecord::NoDatabaseError
  else
    raise
  end
end

#primary_class?Boolean

:nodoc:

Returns:

  • (Boolean)
[View source]

202
203
204
# File 'lib/active_record/connection_handling.rb', line 202

def primary_class? # :nodoc:
  self == Base || defined?(ApplicationRecord) && self == ApplicationRecord
end

#remove_connection(name = nil) ⇒ Object

[View source]

229
230
231
232
233
234
235
236
237
238
239
# File 'lib/active_record/connection_handling.rb', line 229

def remove_connection(name = nil)
  name ||= @connection_specification_name if defined?(@connection_specification_name)
  # if removing a connection that has a pool, we reset the
  # connection_specification_name so it will use the parent
  # pool.
  if connection_handler.retrieve_connection_pool(name)
    self.connection_specification_name = nil
  end

  connection_handler.remove_connection(name)
end

#resolve_config_for_connection(config_or_env) ⇒ Object

:nodoc:

[View source]

162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/active_record/connection_handling.rb', line 162

def resolve_config_for_connection(config_or_env) # :nodoc:
  raise "Anonymous class is not allowed." unless name

  config_or_env ||= DEFAULT_ENV.call.to_sym
  pool_name = primary_class? ? "primary" : name
  self.connection_specification_name = pool_name

  resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
  config_hash = resolver.resolve(config_or_env, pool_name).symbolize_keys
  config_hash[:name] = pool_name

  config_hash
end

#retrieve_connectionObject

[View source]

220
221
222
# File 'lib/active_record/connection_handling.rb', line 220

def retrieve_connection
  connection_handler.retrieve_connection(connection_specification_name)
end

#sqlite3_connection(config) ⇒ Object

[View source]

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/active_record/connection_adapters/sqlite3_adapter.rb', line 18

def sqlite3_connection(config)
  config = config.symbolize_keys

  # Require database.
  unless config[:database]
    raise ArgumentError, "No database file specified. Missing argument: database"
  end

  # Allow database path relative to Rails.root, but only if the database
  # path is not the special path that tells sqlite to build a database only
  # in memory.
  if ":memory:" != config[:database]
    config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
    dirname = File.dirname(config[:database])
    Dir.mkdir(dirname) unless File.directory?(dirname)
  end

  db = SQLite3::Database.new(
    config[:database].to_s,
    config.merge(results_as_hash: true)
  )

  ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
rescue Errno::ENOENT => error
  if error.message.include?("No such file or directory")
    raise ActiveRecord::NoDatabaseError
  else
    raise
  end
end

#with_handler(handler_key, &blk) ⇒ Object

:nodoc:

[View source]

157
158
159
160
# File 'lib/active_record/connection_handling.rb', line 157

def with_handler(handler_key, &blk) # :nodoc:
  handler = lookup_connection_handler(handler_key)
  swap_connection_handler(handler, &blk)
end