Skip to content

Commit 0b2eaf7

Browse files
authored
Merge pull request #188 from leoarnold/leoarnold/meta_request/fragment-caching
Prevent spurious SQL query execution via ActiveSupport::Cache events
2 parents 7ecc156 + 554626d commit 0b2eaf7

File tree

3 files changed

+64
-1
lines changed

3 files changed

+64
-1
lines changed

meta_request/.rubocop.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
inherit_from: .rubocop_todo.yml
2+
3+
Metrics/BlockLength:
4+
ExcludedMethods:
5+
- describe

meta_request/lib/meta_request/event.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require 'active_support'
4+
require 'active_support/cache'
45
require 'active_support/json'
56
require 'active_support/core_ext'
67

@@ -13,6 +14,7 @@ class Event < ActiveSupport::Notifications::Event
1314
attr_reader :duration
1415

1516
def initialize(name, start, ending, transaction_id, payload)
17+
@name = name
1618
super(name, start, ending, transaction_id, json_encodable(payload))
1719
@duration = 1000.0 * (ending - start)
1820
end
@@ -37,7 +39,7 @@ def self.events_for_exception(exception_wrapper)
3739
def json_encodable(payload)
3840
return {} unless payload.is_a?(Hash)
3941

40-
transform_hash(payload, deep: true) do |hash, key, value|
42+
transform_hash(sanitize_hash(payload), deep: true) do |hash, key, value|
4143
if value.class.to_s == 'ActionDispatch::Http::Headers'
4244
value = value.to_h.select { |k, _| k.upcase == k }
4345
elsif not_encodable?(value)
@@ -54,6 +56,14 @@ def json_encodable(payload)
5456
end.with_indifferent_access
5557
end
5658

59+
def sanitize_hash(payload)
60+
if @name =~ /\Acache_\w+\.active_support\z/
61+
payload[:key] = ActiveSupport::Cache::Store.new.send(:normalize_key, payload[:key])
62+
end
63+
64+
payload
65+
end
66+
5767
def not_encodable?(value)
5868
(defined?(ActiveRecord) && value.is_a?(ActiveRecord::ConnectionAdapters::AbstractAdapter)) ||
5969
(defined?(ActionDispatch) &&
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
RSpec.describe MetaRequest::Event do
6+
describe '.new' do
7+
context 'when transforming an event from ActiveSupport::Cache' do
8+
let(:active_support_cache_events) do
9+
%w[
10+
cache_read.active_support
11+
cache_write.active_support
12+
]
13+
end
14+
15+
let(:payload) do
16+
{
17+
key: [
18+
'views',
19+
'users/show:118c2da025706846afc6874e76b33a5c',
20+
active_record_relation
21+
]
22+
}
23+
end
24+
25+
let(:active_record_relation) do
26+
double('ActiveRecord::Relation', cache_key: 'users/query-e06f359ccb226f3021b50c0c7e457f79')
27+
end
28+
29+
let(:full_cache_key) do
30+
'views/users/show:118c2da025706846afc6874e76b33a5c/users/query-e06f359ccb226f3021b50c0c7e457f79'
31+
end
32+
33+
it 'replaces the cache key with its String representation and does not trigger SQL queries' do
34+
# This would result in an SQL query being executed
35+
expect(active_record_relation).to_not receive(:as_json)
36+
37+
event = described_class.new(
38+
active_support_cache_events.sample,
39+
Time.parse('2023-08-02 23:10:00.759479575 +0200'),
40+
Time.parse('2023-08-02 23:10:00.761230028 +0200'),
41+
SecureRandom.hex(10),
42+
payload
43+
)
44+
45+
expect(event.payload[:key]).to eq(full_cache_key)
46+
end
47+
end
48+
end
49+
end

0 commit comments

Comments
 (0)