diff --git a/lib/graphiti/resource/interface.rb b/lib/graphiti/resource/interface.rb index 0f702cf3..46f10773 100644 --- a/lib/graphiti/resource/interface.rb +++ b/lib/graphiti/resource/interface.rb @@ -4,9 +4,10 @@ module Interface extend ActiveSupport::Concern class_methods do - def cache_resource(expires_in: false) + def cache_resource(expires_in: false, tag: nil) @cache_resource = true @cache_expires_in = expires_in + @cache_tag = tag end def all(params = {}, base_scope = nil) @@ -55,7 +56,7 @@ def build(params, base_scope = nil) private def caching_options - {cache: @cache_resource, cache_expires_in: @cache_expires_in} + {cache: @cache_resource, cache_expires_in: @cache_expires_in, cache_tag: @cache_tag} end def validate_request!(params) diff --git a/lib/graphiti/resource_proxy.rb b/lib/graphiti/resource_proxy.rb index b95b5feb..37d469c2 100644 --- a/lib/graphiti/resource_proxy.rb +++ b/lib/graphiti/resource_proxy.rb @@ -2,14 +2,19 @@ module Graphiti class ResourceProxy include Enumerable - attr_reader :resource, :query, :scope, :payload, :cache_expires_in, :cache + attr_reader :resource, :query, :scope, :payload, :cache_expires_in, :cache, :cache_tag - def initialize(resource, scope, query, + def initialize( + resource, + scope, + query, payload: nil, single: false, raise_on_missing: false, cache: nil, - cache_expires_in: nil) + cache_expires_in: nil, + cache_tag: nil + ) @resource = resource @scope = scope @@ -19,6 +24,7 @@ def initialize(resource, scope, query, @raise_on_missing = raise_on_missing @cache = cache @cache_expires_in = cache_expires_in + @cache_tag = cache_tag end def cache? @@ -207,12 +213,30 @@ def etag "W/#{ActiveSupport::Digest.hexdigest(cache_key_with_version.to_s)}" end + def resource_cache_tag + return unless @cache_tag.present? && @resource.respond_to?(@cache_tag) + + @resource.try(@cache_tag) + end + def cache_key - ActiveSupport::Cache.expand_cache_key([@scope.cache_key, @query.cache_key]) + ActiveSupport::Cache.expand_cache_key( + [ + @scope.cache_key, + @query.cache_key, + resource_cache_tag + ].compact_blank + ) end def cache_key_with_version - ActiveSupport::Cache.expand_cache_key([@scope.cache_key_with_version, @query.cache_key]) + ActiveSupport::Cache.expand_cache_key( + [ + @scope.cache_key_with_version, + @query.cache_key, + resource_cache_tag + ].compact_blank + ) end private diff --git a/lib/graphiti/runner.rb b/lib/graphiti/runner.rb index d2b9d070..bce0c26b 100644 --- a/lib/graphiti/runner.rb +++ b/lib/graphiti/runner.rb @@ -58,22 +58,29 @@ def jsonapi_render_options def proxy(base = nil, opts = {}) base ||= jsonapi_resource.base_scope - scope_opts = opts.slice :sideload_parent_length, + scope_opts = opts.slice( + :sideload_parent_length, :default_paginate, :after_resolve, :sideload, :parent, :params, :bypass_required_filters + ) + scope = jsonapi_scope(base, scope_opts) - ResourceProxy.new jsonapi_resource, + + ::Graphiti::ResourceProxy.new( + jsonapi_resource, scope, query, payload: deserialized_payload, single: opts[:single], raise_on_missing: opts[:raise_on_missing], cache: opts[:cache], - cache_expires_in: opts[:cache_expires_in] + cache_expires_in: opts[:cache_expires_in], + cache_tag: opts[:cache_tag] + ) end end end diff --git a/lib/graphiti/util/cache_debug.rb b/lib/graphiti/util/cache_debug.rb index cc7c3783..bedbd2e1 100644 --- a/lib/graphiti/util/cache_debug.rb +++ b/lib/graphiti/util/cache_debug.rb @@ -12,7 +12,9 @@ def last_version end def name - "#{Graphiti.context[:object]&.request&.method} #{Graphiti.context[:object]&.request&.url}" + tag = proxy.resource_cache_tag + + "#{::Graphiti.context[:object]&.request&.method} #{::Graphiti.context[:object]&.request&.url} #{tag}" end def key diff --git a/spec/resource_proxy_spec.rb b/spec/resource_proxy_spec.rb index ee87e37c..5917bef5 100644 --- a/spec/resource_proxy_spec.rb +++ b/spec/resource_proxy_spec.rb @@ -14,13 +14,20 @@ let(:query) { double(cache_key: "query-hash") } let(:scope) { double(cache_key: "scope-hash", cache_key_with_version: "scope-hash-123456") } - subject { described_class.new(resource, scope, query, **{}) } + subject { described_class.new(resource, scope, query, **{cache_tag: :cache_tag}) } - it "cache_key combines query and scope cache keys" do + it "cache_key combines query and scope cache keys if no tags are set" do cache_key = subject.cache_key expect(cache_key).to eq("scope-hash/query-hash") end + it "cache_key combines query, scope and tag cache keys if a tag is set" do + allow(resource).to receive(:cache_tag).and_return("tag_value") + + cache_key = subject.cache_key + expect(cache_key).to eq("scope-hash/query-hash/tag_value") + end + it "generates stable etag" do instance1 = described_class.new(resource, scope, query, **{}) instance2 = described_class.new(resource, scope, query, **{})