Skip to content

Commit 22f41a1

Browse files
committed
Add ActiveRecord::Base.with_connection as a shortcut
Extracted from rails#50793 The leased connection is yielded, and for the duration of the block, any call to `ActiveRecord::Base.connection` will yield that same connection. This is useful to perform a few database operations without causing a connection to be leased for the entire duration of the request or job.
1 parent bbd2be4 commit 22f41a1

File tree

6 files changed

+98
-24
lines changed

6 files changed

+98
-24
lines changed

activerecord/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
* Add `ActiveRecord::Base.with_connection` as a shortcut for leasing a connection for a short duration.
2+
3+
The leased connection is yielded, and for the duration of the block, any call to `ActiveRecord::Base.connection`
4+
will yield that same connection.
5+
6+
This is useful to perform a few database operations without causing a connection to be leased for the
7+
entire duration of the request or job.
8+
9+
*Jean Boussier*
10+
111
* Deprecate `config.active_record.warn_on_records_fetched_greater_than` now that `sql.active_record`
212
notification includes `:row_count` field.
313

activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ def self.add_right_association(name, options)
4242
self.right_reflection = _reflect_on_association(rhs_name)
4343
end
4444

45-
def self.retrieve_connection
46-
left_model.retrieve_connection
45+
def self.connection_pool
46+
left_model.connection_pool
4747
end
4848
}
4949

activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,10 @@ def clear_active_connections!(role = nil)
182182
role = ActiveRecord::Base.current_role
183183
end
184184

185-
each_connection_pool(role).each(&:release_connection)
185+
each_connection_pool(role).each do |pool|
186+
pool.release_connection
187+
pool.disable_query_cache!
188+
end
186189
end
187190

188191
# Clears the cache which maps classes.
@@ -223,20 +226,7 @@ def flush_idle_connections!(role = nil)
223226
# opened and set as the active connection for the class it was defined
224227
# for (not necessarily the current class).
225228
def retrieve_connection(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc:
226-
pool = retrieve_connection_pool(connection_name, role: role, shard: shard)
227-
228-
unless pool
229-
if shard != ActiveRecord::Base.default_shard
230-
message = "No connection pool for '#{connection_name}' found for the '#{shard}' shard."
231-
elsif role != ActiveRecord::Base.default_role
232-
message = "No connection pool for '#{connection_name}' found for the '#{role}' role."
233-
else
234-
message = "No connection pool for '#{connection_name}' found."
235-
end
236-
237-
raise ConnectionNotEstablished, message
238-
end
239-
229+
pool = retrieve_connection_pool(connection_name, role: role, shard: shard, strict: true)
240230
pool.connection
241231
end
242232

@@ -256,9 +246,22 @@ def remove_connection_pool(connection_name, role: ActiveRecord::Base.current_rol
256246
# Retrieving the connection pool happens a lot, so we cache it in @connection_name_to_pool_manager.
257247
# This makes retrieving the connection pool O(1) once the process is warm.
258248
# When a connection is established or removed, we invalidate the cache.
259-
def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
260-
pool_config = get_pool_manager(connection_name)&.get_pool_config(role, shard)
261-
pool_config&.pool
249+
def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard, strict: false)
250+
pool = get_pool_manager(connection_name)&.get_pool_config(role, shard)&.pool
251+
252+
if strict && !pool
253+
if shard != ActiveRecord::Base.default_shard
254+
message = "No connection pool for '#{connection_name}' found for the '#{shard}' shard."
255+
elsif role != ActiveRecord::Base.default_role
256+
message = "No connection pool for '#{connection_name}' found for the '#{role}' role."
257+
else
258+
message = "No connection pool for '#{connection_name}' found."
259+
end
260+
261+
raise ConnectionNotEstablished, message
262+
end
263+
264+
pool
262265
end
263266

264267
private

activerecord/lib/active_record/connection_handling.rb

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,15 +243,22 @@ def connected_to?(role:, shard: ActiveRecord::Base.default_shard)
243243
# Clears the query cache for all connections associated with the current thread.
244244
def clear_query_caches_for_current_thread
245245
connection_handler.each_connection_pool do |pool|
246-
pool.connection.clear_query_cache if pool.active_connection?
246+
pool.connection.clear_query_cache
247247
end
248248
end
249249

250250
# Returns the connection currently associated with the class. This can
251251
# also be used to "borrow" the connection to do database work unrelated
252252
# to any of the specific Active Records.
253253
def connection
254-
retrieve_connection
254+
connection_pool.connection
255+
end
256+
257+
# Checkouts a connection from the pool, yield it and then check it back in.
258+
# If a connection was already leased via #connection or a parent call to
259+
# #with_connection, that same connection is yieled.
260+
def with_connection(&block) # :nodoc:
261+
connection_pool.with_connection(&block)
255262
end
256263

257264
attr_writer :connection_specification_name
@@ -280,7 +287,7 @@ def connection_db_config
280287
end
281288

282289
def connection_pool
283-
connection_handler.retrieve_connection_pool(connection_specification_name, role: current_role, shard: current_shard) || raise(ConnectionNotEstablished)
290+
connection_handler.retrieve_connection_pool(connection_specification_name, role: current_role, shard: current_shard, strict: true)
284291
end
285292

286293
def retrieve_connection

activerecord/lib/active_record/transactions.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,9 @@ module Transactions
209209
module ClassMethods
210210
# See the ConnectionAdapters::DatabaseStatements#transaction API docs.
211211
def transaction(**options, &block)
212-
connection.transaction(**options, &block)
212+
with_connection do |connection|
213+
connection.transaction(**options, &block)
214+
end
213215
end
214216

215217
def before_commit(*args, &block) # :nodoc:
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# frozen_string_literal: true
2+
3+
require "cases/helper"
4+
5+
module ActiveRecord
6+
class ConnectionHandlingTest < ActiveRecord::TestCase
7+
unless in_memory_db?
8+
test "#with_connection lease the connection for the duration of the block" do
9+
ActiveRecord::Base.connection_pool.release_connection
10+
assert_not_predicate ActiveRecord::Base.connection_pool, :active_connection?
11+
12+
ActiveRecord::Base.with_connection do |connection|
13+
assert_predicate ActiveRecord::Base.connection_pool, :active_connection?
14+
assert_same connection, ActiveRecord::Base.connection
15+
end
16+
17+
assert_not_predicate ActiveRecord::Base.connection_pool, :active_connection?
18+
end
19+
20+
test "#with_connection use the already leased connection if available" do
21+
leased_connection = ActiveRecord::Base.connection
22+
assert_predicate ActiveRecord::Base.connection_pool, :active_connection?
23+
24+
ActiveRecord::Base.with_connection do |connection|
25+
assert_same leased_connection, connection
26+
assert_same ActiveRecord::Base.connection, connection
27+
end
28+
29+
assert_predicate ActiveRecord::Base.connection_pool, :active_connection?
30+
assert_same ActiveRecord::Base.connection, leased_connection
31+
end
32+
33+
test "#with_connection is reentrant" do
34+
leased_connection = ActiveRecord::Base.connection
35+
assert_predicate ActiveRecord::Base.connection_pool, :active_connection?
36+
37+
ActiveRecord::Base.with_connection do |connection|
38+
assert_same leased_connection, connection
39+
assert_same ActiveRecord::Base.connection, connection
40+
41+
ActiveRecord::Base.with_connection do |connection2|
42+
assert_same leased_connection, connection
43+
assert_same ActiveRecord::Base.connection, connection
44+
end
45+
end
46+
47+
assert_predicate ActiveRecord::Base.connection_pool, :active_connection?
48+
assert_same ActiveRecord::Base.connection, leased_connection
49+
end
50+
end
51+
end
52+
end

0 commit comments

Comments
 (0)