Skip to content

Commit 6bad959

Browse files
committed
Extract ActiveSupport::ExecutionContext out of ActiveRecord::QueryLogs
I'm working on a standardized error reporting interface (rails#43472) and it has the same need for a `context` than Active Record's query logs. Just like query logs, when reporting an error you want to know what the current controller or job is, etc. So by extracting it we can allow both API to use the same store.
1 parent f5a7d2e commit 6bad959

File tree

16 files changed

+151
-141
lines changed

16 files changed

+151
-141
lines changed

actionpack/lib/abstract_controller/base.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ def process(action, *args)
148148

149149
@_response_body = nil
150150

151+
ActiveSupport::ExecutionContext[:controller] = self
151152
process_action(action_name, *args)
152153
end
153154

actionpack/lib/action_controller.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ module ActionController
3737
autoload :Logging
3838
autoload :MimeResponds
3939
autoload :ParamsWrapper
40-
autoload :QueryTags
4140
autoload :Redirecting
4241
autoload :Renderers
4342
autoload :Rendering

actionpack/lib/action_controller/metal/query_tags.rb

Lines changed: 0 additions & 16 deletions
This file was deleted.

actionpack/lib/action_controller/railtie.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,6 @@ class Railtie < Rails::Railtie # :nodoc:
113113
if query_logs_tags_enabled
114114
app.config.active_record.query_log_tags += [:controller, :action]
115115

116-
ActiveSupport.on_load(:action_controller) do
117-
include ActionController::QueryTags
118-
end
119-
120116
ActiveSupport.on_load(:active_record) do
121117
ActiveRecord::QueryLogs.taggings.merge!(
122118
controller: ->(context) { context[:controller]&.controller_name },

activejob/lib/active_job/execution.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def perform(*)
5454

5555
private
5656
def _perform_job
57+
ActiveSupport::ExecutionContext[:job] = self
5758
run_callbacks :perform do
5859
perform(*arguments)
5960
end

activejob/lib/active_job/query_tags.rb

Lines changed: 0 additions & 16 deletions
This file was deleted.

activejob/lib/active_job/railtie.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,6 @@ class Railtie < Rails::Railtie # :nodoc:
6565
if query_logs_tags_enabled
6666
app.config.active_record.query_log_tags << :job
6767

68-
ActiveSupport.on_load(:active_job) do
69-
include ActiveJob::QueryTags
70-
end
71-
7268
ActiveSupport.on_load(:active_record) do
7369
ActiveRecord::QueryLogs.taggings[:job] = ->(context) { context[:job].class.name if context[:job] }
7470
end

activerecord/lib/active_record/query_logs.rb

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,17 @@ module ActiveRecord
4444
# ]
4545
# ActiveRecord::QueryLogs.tags = tags
4646
#
47-
# The QueryLogs +context+ can be manipulated via the +set_context+ method.
47+
# The QueryLogs +context+ can be manipulated via the +ActiveSupport::ExecutionContext.set+ method.
4848
#
4949
# Temporary updates limited to the execution of a block:
5050
#
51-
# ActiveRecord::QueryLogs.set_context(foo: Bar.new) do
51+
# ActiveSupport::ExecutionContext.set(foo: Bar.new) do
5252
# posts = Post.all
5353
# end
5454
#
5555
# Direct updates to a context value:
5656
#
57-
# ActiveRecord::QueryLogs.set_context(foo: Bar.new)
57+
# ActiveSupport::ExecutionContext[:foo] = Bar.new
5858
#
5959
# Tag comments can be prepended to the query:
6060
#
@@ -76,30 +76,6 @@ module QueryLogs
7676
thread_mattr_accessor :cached_comment, instance_accessor: false
7777

7878
class << self
79-
# Updates the context used to construct tags in the SQL comment.
80-
# If a block is given, it resets the provided keys to their
81-
# previous value once the block exits.
82-
def set_context(**options)
83-
options.symbolize_keys!
84-
85-
keys = options.keys
86-
previous_context = keys.zip(context.values_at(*keys)).to_h
87-
context.merge!(options)
88-
self.cached_comment = nil
89-
if block_given?
90-
begin
91-
yield
92-
ensure
93-
context.merge!(previous_context)
94-
self.cached_comment = nil
95-
end
96-
end
97-
end
98-
99-
def clear_context # :nodoc:
100-
context.clear
101-
end
102-
10379
def call(sql) # :nodoc:
10480
if prepend_comment
10581
"#{self.comment} #{sql}"
@@ -108,6 +84,12 @@ def call(sql) # :nodoc:
10884
end.strip
10985
end
11086

87+
def clear_cache # :nodoc:
88+
self.cached_comment = nil
89+
end
90+
91+
ActiveSupport::ExecutionContext.after_change { ActiveRecord::QueryLogs.clear_cache }
92+
11193
private
11294
# Returns an SQL comment +String+ containing the query log tags.
11395
# Sets and returns a cached comment if <tt>cache_query_log_tags</tt> is +true+.
@@ -126,15 +108,13 @@ def uncached_comment
126108
end
127109
end
128110

129-
def context
130-
Thread.current[:active_record_query_log_tags_context] ||= {}
131-
end
132-
133111
def escape_sql_comment(content)
134112
content.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
135113
end
136114

137115
def tag_content
116+
context = ActiveSupport::ExecutionContext.to_h
117+
138118
tags.flat_map { |i| [*i] }.filter_map do |tag|
139119
key, handler = tag
140120
handler ||= taggings[key]

activerecord/lib/active_record/railtie.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -376,10 +376,6 @@ class Railtie < Rails::Railtie # :nodoc:
376376
if app.config.active_record.cache_query_log_tags
377377
ActiveRecord::QueryLogs.cache_query_log_tags = true
378378
end
379-
380-
app.reloader.before_class_unload { ActiveRecord::QueryLogs.clear_context }
381-
app.executor.to_run { ActiveRecord::QueryLogs.clear_context }
382-
app.executor.to_complete { ActiveRecord::QueryLogs.clear_context }
383379
end
384380
end
385381
end

activerecord/test/cases/query_logs_test.rb

Lines changed: 13 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ class QueryLogsTest < ActiveRecord::TestCase
1111
}
1212

1313
def setup
14-
# QueryLogs context is automatically reset in Rails app via an executor hooks set in railtie
14+
# ActiveSupport::ExecutionContext context is automatically reset in Rails app via an executor hooks set in railtie
1515
# But not in Active Record's own test suite.
16-
ActiveRecord::QueryLogs.clear_context
16+
ActiveSupport::ExecutionContext.clear
1717

1818
# Enable the query tags logging
1919
@original_transformers = ActiveRecord.query_transformers
2020
@original_prepend = ActiveRecord::QueryLogs.prepend_comment
21+
@original_tags = ActiveRecord::QueryLogs.tags
2122
ActiveRecord.query_transformers += [ActiveRecord::QueryLogs]
2223
ActiveRecord::QueryLogs.prepend_comment = false
2324
ActiveRecord::QueryLogs.cache_query_log_tags = false
@@ -27,10 +28,13 @@ def setup
2728
def teardown
2829
ActiveRecord.query_transformers = @original_transformers
2930
ActiveRecord::QueryLogs.prepend_comment = @original_prepend
30-
ActiveRecord::QueryLogs.tags = []
31-
# QueryLogs context is automatically reset in Rails app via an executor hooks set in railtie
31+
ActiveRecord::QueryLogs.tags = @original_tags
32+
ActiveRecord::QueryLogs.prepend_comment = false
33+
ActiveRecord::QueryLogs.cache_query_log_tags = false
34+
ActiveRecord::QueryLogs.cached_comment = nil
35+
# ActiveSupport::ExecutionContext context is automatically reset in Rails app via an executor hooks set in railtie
3236
# But not in Active Record's own test suite.
33-
ActiveRecord::QueryLogs.clear_context
37+
ActiveSupport::ExecutionContext.clear
3438
end
3539

3640
def test_escaping_good_comment
@@ -57,8 +61,6 @@ def test_add_comments_to_beginning_of_query
5761
assert_sql(%r{/\*application:active_record\*/ select id from posts$}) do
5862
ActiveRecord::Base.connection.execute "select id from posts"
5963
end
60-
ensure
61-
ActiveRecord::QueryLogs.prepend_comment = nil
6264
end
6365

6466
def test_exists_is_commented
@@ -112,41 +114,24 @@ def test_retrieves_comment_from_cache_when_enabled_and_set
112114
ActiveRecord::QueryLogs.stub(:cached_comment, "/*cached_comment*/") do
113115
assert_equal "/*cached_comment*/", ActiveRecord::QueryLogs.call("")
114116
end
115-
ensure
116-
ActiveRecord::QueryLogs.cached_comment = nil
117-
ActiveRecord::QueryLogs.cache_query_log_tags = false
118117
end
119118

120119
def test_resets_cache_on_context_update
121120
ActiveRecord::QueryLogs.cache_query_log_tags = true
122-
ActiveRecord::QueryLogs.set_context(temporary: "value")
121+
ActiveSupport::ExecutionContext[:temporary] = "value"
123122
ActiveRecord::QueryLogs.tags = [ temporary_tag: ->(context) { context[:temporary] } ]
124123

125124
assert_equal "/*temporary_tag:value*/", ActiveRecord::QueryLogs.call("")
126125

127-
ActiveRecord::QueryLogs.set_context(temporary: "new_value")
126+
ActiveSupport::ExecutionContext[:temporary] = "new_value"
128127

129128
assert_nil ActiveRecord::QueryLogs.cached_comment
130129
assert_equal "/*temporary_tag:new_value*/", ActiveRecord::QueryLogs.call("")
131-
ensure
132-
ActiveRecord::QueryLogs.cached_comment = nil
133-
ActiveRecord::QueryLogs.cache_query_log_tags = false
134-
end
135-
136-
def test_ensure_context_has_symbol_keys
137-
ActiveRecord::QueryLogs.tags = [ new_key: ->(context) { context[:symbol_key] } ]
138-
ActiveRecord::QueryLogs.set_context("symbol_key" => "symbolized")
139-
140-
assert_sql(%r{/\*new_key:symbolized}) do
141-
Dashboard.first
142-
end
143-
ensure
144-
ActiveRecord::QueryLogs.set_context(application_name: nil)
145130
end
146131

147132
def test_default_tag_behavior
148133
ActiveRecord::QueryLogs.tags = [:application, :foo]
149-
ActiveRecord::QueryLogs.set_context(foo: "bar") do
134+
ActiveSupport::ExecutionContext.set(foo: "bar") do
150135
assert_sql(%r{/\*application:active_record,foo:bar\*/}) do
151136
Dashboard.first
152137
end
@@ -157,39 +142,29 @@ def test_default_tag_behavior
157142
end
158143

159144
def test_empty_comments_are_not_added
160-
original_tags = ActiveRecord::QueryLogs.tags
161145
ActiveRecord::QueryLogs.tags = [ empty: -> { nil } ]
162146
assert_sql(%r{select id from posts$}) do
163147
ActiveRecord::Base.connection.execute "select id from posts"
164148
end
165-
ensure
166-
ActiveRecord::QueryLogs.tags = original_tags
167149
end
168150

169151
def test_custom_basic_tags
170-
original_tags = ActiveRecord::QueryLogs.tags
171152
ActiveRecord::QueryLogs.tags = [ :application, { custom_string: "test content" } ]
172153

173154
assert_sql(%r{/\*application:active_record,custom_string:test content\*/$}) do
174155
Dashboard.first
175156
end
176-
ensure
177-
ActiveRecord::QueryLogs.tags = original_tags
178157
end
179158

180159
def test_custom_proc_tags
181-
original_tags = ActiveRecord::QueryLogs.tags
182160
ActiveRecord::QueryLogs.tags = [ :application, { custom_proc: -> { "test content" } } ]
183161

184162
assert_sql(%r{/\*application:active_record,custom_proc:test content\*/$}) do
185163
Dashboard.first
186164
end
187-
ensure
188-
ActiveRecord::QueryLogs.tags = original_tags
189165
end
190166

191167
def test_multiple_custom_tags
192-
original_tags = ActiveRecord::QueryLogs.tags
193168
ActiveRecord::QueryLogs.tags = [
194169
:application,
195170
{ custom_proc: -> { "test content" }, another_proc: -> { "more test content" } },
@@ -198,34 +173,14 @@ def test_multiple_custom_tags
198173
assert_sql(%r{/\*application:active_record,custom_proc:test content,another_proc:more test content\*/$}) do
199174
Dashboard.first
200175
end
201-
ensure
202-
ActiveRecord::QueryLogs.tags = original_tags
203176
end
204177

205178
def test_custom_proc_context_tags
206-
original_tags = ActiveRecord::QueryLogs.tags
207-
ActiveRecord::QueryLogs.set_context(foo: "bar")
179+
ActiveSupport::ExecutionContext[:foo] = "bar"
208180
ActiveRecord::QueryLogs.tags = [ :application, { custom_context_proc: ->(context) { context[:foo] } } ]
209181

210182
assert_sql(%r{/\*application:active_record,custom_context_proc:bar\*/$}) do
211183
Dashboard.first
212184
end
213-
ensure
214-
ActiveRecord::QueryLogs.set_context(foo: nil)
215-
ActiveRecord::QueryLogs.tags = original_tags
216-
end
217-
218-
def test_set_context_restore_state
219-
original_tags = ActiveRecord::QueryLogs.tags
220-
ActiveRecord::QueryLogs.tags = [foo: ->(context) { context[:foo] }]
221-
ActiveRecord::QueryLogs.set_context(foo: "bar") do
222-
assert_sql(%r{/\*foo:bar\*/$}) { Dashboard.first }
223-
ActiveRecord::QueryLogs.set_context(foo: "plop") do
224-
assert_sql(%r{/\*foo:plop\*/$}) { Dashboard.first }
225-
end
226-
assert_sql(%r{/\*foo:bar\*/$}) { Dashboard.first }
227-
end
228-
ensure
229-
ActiveRecord::QueryLogs.tags = original_tags
230185
end
231186
end

0 commit comments

Comments
 (0)