Skip to content

Commit e8c2bad

Browse files
authored
feat: add cache tag support to allow context-aware caching (#498)
1 parent 511ebcd commit e8c2bad

File tree

5 files changed

+54
-13
lines changed

5 files changed

+54
-13
lines changed

lib/graphiti/resource/interface.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ module Interface
44
extend ActiveSupport::Concern
55

66
class_methods do
7-
def cache_resource(expires_in: false)
7+
def cache_resource(expires_in: false, tag: nil)
88
@cache_resource = true
99
@cache_expires_in = expires_in
10+
@cache_tag = tag
1011
end
1112

1213
def all(params = {}, base_scope = nil)
@@ -55,7 +56,7 @@ def build(params, base_scope = nil)
5556
private
5657

5758
def caching_options
58-
{cache: @cache_resource, cache_expires_in: @cache_expires_in}
59+
{cache: @cache_resource, cache_expires_in: @cache_expires_in, cache_tag: @cache_tag}
5960
end
6061

6162
def validate_request!(params)

lib/graphiti/resource_proxy.rb

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@ module Graphiti
22
class ResourceProxy
33
include Enumerable
44

5-
attr_reader :resource, :query, :scope, :payload, :cache_expires_in, :cache
5+
attr_reader :resource, :query, :scope, :payload, :cache_expires_in, :cache, :cache_tag
66

7-
def initialize(resource, scope, query,
7+
def initialize(
8+
resource,
9+
scope,
10+
query,
811
payload: nil,
912
single: false,
1013
raise_on_missing: false,
1114
cache: nil,
12-
cache_expires_in: nil)
15+
cache_expires_in: nil,
16+
cache_tag: nil
17+
)
1318

1419
@resource = resource
1520
@scope = scope
@@ -19,6 +24,7 @@ def initialize(resource, scope, query,
1924
@raise_on_missing = raise_on_missing
2025
@cache = cache
2126
@cache_expires_in = cache_expires_in
27+
@cache_tag = cache_tag
2228
end
2329

2430
def cache?
@@ -207,12 +213,30 @@ def etag
207213
"W/#{ActiveSupport::Digest.hexdigest(cache_key_with_version.to_s)}"
208214
end
209215

216+
def resource_cache_tag
217+
return unless @cache_tag.present? && @resource.respond_to?(@cache_tag)
218+
219+
@resource.try(@cache_tag)
220+
end
221+
210222
def cache_key
211-
ActiveSupport::Cache.expand_cache_key([@scope.cache_key, @query.cache_key])
223+
ActiveSupport::Cache.expand_cache_key(
224+
[
225+
@scope.cache_key,
226+
@query.cache_key,
227+
resource_cache_tag
228+
].compact_blank
229+
)
212230
end
213231

214232
def cache_key_with_version
215-
ActiveSupport::Cache.expand_cache_key([@scope.cache_key_with_version, @query.cache_key])
233+
ActiveSupport::Cache.expand_cache_key(
234+
[
235+
@scope.cache_key_with_version,
236+
@query.cache_key,
237+
resource_cache_tag
238+
].compact_blank
239+
)
216240
end
217241

218242
private

lib/graphiti/runner.rb

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,22 +58,29 @@ def jsonapi_render_options
5858

5959
def proxy(base = nil, opts = {})
6060
base ||= jsonapi_resource.base_scope
61-
scope_opts = opts.slice :sideload_parent_length,
61+
scope_opts = opts.slice(
62+
:sideload_parent_length,
6263
:default_paginate,
6364
:after_resolve,
6465
:sideload,
6566
:parent,
6667
:params,
6768
:bypass_required_filters
69+
)
70+
6871
scope = jsonapi_scope(base, scope_opts)
69-
ResourceProxy.new jsonapi_resource,
72+
73+
::Graphiti::ResourceProxy.new(
74+
jsonapi_resource,
7075
scope,
7176
query,
7277
payload: deserialized_payload,
7378
single: opts[:single],
7479
raise_on_missing: opts[:raise_on_missing],
7580
cache: opts[:cache],
76-
cache_expires_in: opts[:cache_expires_in]
81+
cache_expires_in: opts[:cache_expires_in],
82+
cache_tag: opts[:cache_tag]
83+
)
7784
end
7885
end
7986
end

lib/graphiti/util/cache_debug.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ def last_version
1212
end
1313

1414
def name
15-
"#{Graphiti.context[:object]&.request&.method} #{Graphiti.context[:object]&.request&.url}"
15+
tag = proxy.resource_cache_tag
16+
17+
"#{::Graphiti.context[:object]&.request&.method} #{::Graphiti.context[:object]&.request&.url} #{tag}"
1618
end
1719

1820
def key

spec/resource_proxy_spec.rb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,20 @@
1414
let(:query) { double(cache_key: "query-hash") }
1515
let(:scope) { double(cache_key: "scope-hash", cache_key_with_version: "scope-hash-123456") }
1616

17-
subject { described_class.new(resource, scope, query, **{}) }
17+
subject { described_class.new(resource, scope, query, **{cache_tag: :cache_tag}) }
1818

19-
it "cache_key combines query and scope cache keys" do
19+
it "cache_key combines query and scope cache keys if no tags are set" do
2020
cache_key = subject.cache_key
2121
expect(cache_key).to eq("scope-hash/query-hash")
2222
end
2323

24+
it "cache_key combines query, scope and tag cache keys if a tag is set" do
25+
allow(resource).to receive(:cache_tag).and_return("tag_value")
26+
27+
cache_key = subject.cache_key
28+
expect(cache_key).to eq("scope-hash/query-hash/tag_value")
29+
end
30+
2431
it "generates stable etag" do
2532
instance1 = described_class.new(resource, scope, query, **{})
2633
instance2 = described_class.new(resource, scope, query, **{})

0 commit comments

Comments
 (0)