lib/jsonapi/cached_response_fragment.rb
# frozen_string_literal: true
module JSONAPI
class CachedResponseFragment
Lookup = Struct.new(:resource_klass, :serializer_config_key, :context, :context_key, :cache_ids) do
def type
resource_klass._type
end
def keys
cache_ids.map do |(id, cache_key)|
[type, id, cache_key, serializer_config_key, context_key]
end
end
end
Write = Struct.new(:resource_klass, :resource, :serializer, :serializer_config_key, :context, :context_key, :relationship_data) do
def to_key_value
(id, cache_key) = resource.cache_id
json = serializer.object_hash(resource, relationship_data)
cr = CachedResponseFragment.new(
resource_klass,
id,
json['type'],
context,
resource.fetchable_fields,
json['relationships'],
json['links'],
json['attributes'],
json['meta']
)
key = [resource_klass._type, id, cache_key, serializer_config_key, context_key]
[key, cr]
end
end
attr_reader :resource_klass, :id, :type, :context, :fetchable_fields, :relationships,
:links_json, :attributes_json, :meta_json
def initialize(resource_klass, id, type, context, fetchable_fields, relationships,
links_json, attributes_json, meta_json)
@resource_klass = resource_klass
@id = id
@type = type
@context = context
@fetchable_fields = Set.new(fetchable_fields)
# Relationships left uncompiled because we'll often want to insert included ids on retrieval
@relationships = relationships
@links_json = CompiledJson.of(links_json)
@attributes_json = CompiledJson.of(attributes_json)
@meta_json = CompiledJson.of(meta_json)
end
def to_cache_value
{
id: id,
type: type,
fetchable: fetchable_fields,
rels: relationships,
links: links_json.try(:to_s),
attrs: attributes_json.try(:to_s),
meta: meta_json.try(:to_s)
}
end
# @param [Lookup[]] lookups
# @return [Hash<Class<Resource>, Hash<ID, CachedResourceFragment>>]
def self.lookup(lookups, context)
type_to_klass = lookups.map {|l| [l.type, l.resource_klass]}.to_h
keys = lookups.map(&:keys).flatten(1)
hits = JSONAPI.configuration.resource_cache.read_multi(*keys).reject {|_, v| v.nil?}
return keys.inject({}) do |hash, key|
(type, id, _, _) = key
resource_klass = type_to_klass[type]
hash[resource_klass] ||= {}
if hits.has_key?(key)
hash[resource_klass][id] = self.from_cache_value(resource_klass, context, hits[key])
else
hash[resource_klass][id] = nil
end
hash
end
end
# @param [Write[]] lookups
def self.write(writes)
key_values = writes.map(&:to_key_value)
to_write = key_values.map {|(k, v)| [k, v.to_cache_value]}.to_h
if JSONAPI.configuration.resource_cache.respond_to? :write_multi
JSONAPI.configuration.resource_cache.write_multi(to_write)
else
to_write.each do |key, value|
JSONAPI.configuration.resource_cache.write(key, value)
end
end
end
def self.from_cache_value(resource_klass, context, h)
new(
resource_klass,
h.fetch(:id),
h.fetch(:type),
context,
h.fetch(:fetchable),
h.fetch(:rels, nil),
h.fetch(:links, nil),
h.fetch(:attrs, nil),
h.fetch(:meta, nil)
)
end
end
end