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" }

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#connection_specification_nameObject

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


286
287
288
289
290
291
# File 'lib/active_record/connection_handling.rb', line 286

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

Instance Method Details

#clear_cache!Object

:nodoc:

[View source]

333
334
335
# File 'lib/active_record/connection_handling.rb', line 333

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]

266
267
268
269
270
271
272
273
274
# File 'lib/active_record/connection_handling.rb', line 266

def clear_query_caches_for_current_thread
  if ActiveRecord.legacy_connection_handling
    ActiveRecord::Base.connection_handlers.each_value do |handler|
      clear_on_handler(handler)
    end
  else
    clear_on_handler(ActiveRecord::Base.connection_handler)
  end
end

#connected?Boolean

Returns true if Active Record is connected.

Returns:

  • (Boolean)
[View source]

317
318
319
# File 'lib/active_record/connection_handling.rb', line 317

def connected?
  connection_handler.connected?(connection_specification_name, role: current_role, shard: current_shard)
end

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

Connects to a role (e.g. writing, reading, or a custom role) and/or shard for the duration of the block. At the end of the block the connection will be returned to the original role / shard.

If only a role is passed, Active Record will look up the connection based on the requested role. If a non-established role is requested an ActiveRecord::ConnectionNotEstablished error will be raised:

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

When swapping to a shard, the role must be passed as well. If a non-existent shard is passed, an ActiveRecord::ConnectionNotEstablished error will be raised.

When a shard and role is passed, Active Record will first lookup the role, and then look up the connection by shard key.

ActiveRecord::Base.connected_to(role: :reading, shard: :shard_one_replica) do
  Dog.first # finds first Dog record stored on the shard one replica
end
[View source]

137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/active_record/connection_handling.rb', line 137

def connected_to(role: nil, shard: nil, prevent_writes: false, &blk)
  if ActiveRecord.legacy_connection_handling
    if self != Base
      raise NotImplementedError, "`connected_to` can only be called on ActiveRecord::Base with legacy connection handling."
    end
  else
    if self != Base && !abstract_class
      raise NotImplementedError, "calling `connected_to` is only allowed on ActiveRecord::Base or abstract classes."
    end

    if name != connection_specification_name && !primary_class?
      raise NotImplementedError, "calling `connected_to` is only allowed on the abstract class that established the connection."
    end
  end

  unless role || shard
    raise ArgumentError, "must provide a `shard` and/or `role`."
  end

  with_role_and_shard(role, shard, prevent_writes, &blk)
end

#connected_to?(role:, shard: ActiveRecord::Base.default_shard) ⇒ 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]

252
253
254
# File 'lib/active_record/connection_handling.rb', line 252

def connected_to?(role:, shard: ActiveRecord::Base.default_shard)
  current_role == role.to_sym && current_shard == shard.to_sym
end

#connected_to_many(*classes, role:, shard: nil, prevent_writes: false) ⇒ Object

Connects a role and/or shard to the provided connection names. Optionally prevent_writes can be passed to block writes on a connection. reading will automatically set prevent_writes to true.

connected_to_many is an alternative to deeply nested connected_to blocks.

Usage:

ActiveRecord::Base.connected_to_many(AnimalsRecord, MealsRecord, role: :reading) do
  Dog.first # Read from animals replica
  Dinner.first # Read from meals replica
  Person.first # Read from primary writer
end
[View source]

172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/active_record/connection_handling.rb', line 172

def connected_to_many(*classes, role:, shard: nil, prevent_writes: false)
  classes = classes.flatten

  if ActiveRecord.legacy_connection_handling
    raise NotImplementedError, "connected_to_many is not available with legacy connection handling"
  end

  if self != Base || classes.include?(Base)
    raise NotImplementedError, "connected_to_many can only be called on ActiveRecord::Base."
  end

  prevent_writes = true if role == ActiveRecord.reading_role

  append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes)
  yield
ensure
  connected_to_stack.pop
end

#connecting_to(role: default_role, shard: default_shard, prevent_writes: false) ⇒ Object

Use a specified connection.

This method is useful for ensuring that a specific connection is being used. For example, when booting a console in readonly mode.

It is not recommended to use this method in a request since it does not yield to a block like connected_to.

[View source]

198
199
200
201
202
203
204
205
206
# File 'lib/active_record/connection_handling.rb', line 198

def connecting_to(role: default_role, shard: default_shard, prevent_writes: false)
  if ActiveRecord.legacy_connection_handling
    raise NotImplementedError, "`connecting_to` is not available with `legacy_connection_handling`."
  end

  prevent_writes = true if role == ActiveRecord.reading_role

  append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self])
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]

279
280
281
# File 'lib/active_record/connection_handling.rb', line 279

def connection
  retrieve_connection
end

#connection_db_configObject

Returns the db_config object from the associated connection:

ActiveRecord::Base.connection_db_config
  #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
    @name="primary", @config={pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"}>

Use only for reading.

[View source]

304
305
306
# File 'lib/active_record/connection_handling.rb', line 304

def connection_db_config
  connection_pool.db_config
end

#connection_poolObject

[View source]

308
309
310
# File 'lib/active_record/connection_handling.rb', line 308

def connection_pool
  connection_handler.retrieve_connection_pool(connection_specification_name, role: current_role, shard: current_shard) || raise(ConnectionNotEstablished)
end

#connects_to(database: {}, shards: {}) ⇒ 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

connects_to also supports horizontal sharding. The horizontal sharding API also supports read replicas. Connect a model to a list of shards like this:

class AnimalsModel < ApplicationRecord
  self.abstract_class = true

  connects_to shards: {
    default: { writing: :primary, reading: :primary_replica },
    shard_two: { writing: :primary_shard_two, reading: :primary_shard_replica_two }
  }
end

Returns an array of database connections.

Raises:

  • (NotImplementedError)
[View source]

81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/active_record/connection_handling.rb', line 81

def connects_to(database: {}, shards: {})
  raise NotImplementedError, "`connects_to` can only be called on ActiveRecord::Base or abstract classes" unless self == Base || abstract_class?

  if database.present? && shards.present?
    raise ArgumentError, "`connects_to` can only accept a `database` or `shards` argument, but not both arguments."
  end

  connections = []

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

    self.connection_class = true
    connections << handler.establish_connection(db_config, owner_name: owner_name, role: role)
  end

  shards.each do |shard, database_keys|
    database_keys.each do |role, database_key|
      db_config, owner_name = resolve_config_for_connection(database_key)
      handler = lookup_connection_handler(role.to_sym)

      self.connection_class = true
      connections << handler.establish_connection(db_config, owner_name: owner_name, role: role, shard: shard.to_sym)
    end
  end

  connections
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
53
# File 'lib/active_record/connection_handling.rb', line 49

def establish_connection(config_or_env = nil)
  config_or_env ||= DEFAULT_ENV.call.to_sym
  db_config, owner_name = resolve_config_for_connection(config_or_env)
  connection_handler.establish_connection(db_config, owner_name: owner_name, role: current_role, shard: current_shard)
end

#lookup_connection_handler(handler_key) ⇒ Object

:nodoc:

[View source]

256
257
258
259
260
261
262
263
# File 'lib/active_record/connection_handling.rb', line 256

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

#mysql2_connection(config) ⇒ Object

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

[View source]

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

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

  ConnectionAdapters::Mysql2Adapter.new(
    ConnectionAdapters::Mysql2Adapter.new_client(config),
    logger,
    nil,
    config,
  )
end

#postgresql_connection(config) ⇒ Object

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

[View source]

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/active_record/connection_adapters/postgresql_adapter.rb', line 25

def postgresql_connection(config)
  conn_params = config.symbolize_keys.compact

  # 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)

  ConnectionAdapters::PostgreSQLAdapter.new(
    ConnectionAdapters::PostgreSQLAdapter.new_client(conn_params),
    logger,
    conn_params,
    config,
  )
end

#primary_class?Boolean

:nodoc:

Returns:

  • (Boolean)
[View source]

293
294
295
# File 'lib/active_record/connection_handling.rb', line 293

def primary_class? # :nodoc:
  self == Base || application_record_class?
end

#prohibit_shard_swapping(enabled = true) ⇒ Object

Prohibit swapping shards while inside of the passed block.

In some cases you may want to be able to swap shards but not allow a nested call to connected_to or connected_to_many to swap again. This is useful in cases you're using sharding to provide per-request database isolation.

[View source]

214
215
216
217
218
219
220
# File 'lib/active_record/connection_handling.rb', line 214

def prohibit_shard_swapping(enabled = true)
  prev_value = ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping]
  ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping] = enabled
  yield
ensure
  ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping] = prev_value
end

#remove_connection(name = nil) ⇒ Object

[View source]

321
322
323
324
325
326
327
328
329
330
331
# File 'lib/active_record/connection_handling.rb', line 321

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, role: current_role, shard: current_shard)
    self.connection_specification_name = nil
  end

  connection_handler.remove_connection_pool(name, role: current_role, shard: current_shard)
end

#retrieve_connectionObject

[View source]

312
313
314
# File 'lib/active_record/connection_handling.rb', line 312

def retrieve_connection
  connection_handler.retrieve_connection(connection_specification_name, role: current_role, shard: current_shard)
end

#shard_swapping_prohibited?Boolean

Determine whether or not shard swapping is currently prohibited

Returns:

  • (Boolean)
[View source]

223
224
225
# File 'lib/active_record/connection_handling.rb', line 223

def shard_swapping_prohibited?
  ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping]
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].to_s.start_with?("file:")
    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

#while_preventing_writes(enabled = true, &block) ⇒ Object

Prevent writing to the database regardless of role.

In some cases you may want to prevent writes to the database even if you are on a database that can write. while_preventing_writes will prevent writes to the database for the duration of the block.

This method does not provide the same protection as a readonly user and is meant to be a safeguard against accidental writes.

See READ_QUERY for the queries that are blocked by this method.

[View source]

238
239
240
241
242
243
244
# File 'lib/active_record/connection_handling.rb', line 238

def while_preventing_writes(enabled = true, &block)
  if ActiveRecord.legacy_connection_handling
    connection_handler.while_preventing_writes(enabled, &block)
  else
    connected_to(role: current_role, prevent_writes: enabled, &block)
  end
end