Fix cache keys to prevent fieldset caching errors
This change alters the cache namespace prior to retrieving cached record data to ensure that different fieldsets are given different cache keys. Previously, all cache keys for the same record would be specified identically, leading to a situation where the fieldset would be ignored if record caching is enabled. Fixes #90.
This commit is contained in:
parent
1ce4677a22
commit
8401d16c2e
@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Changed
|
||||
- Remove `ObjectSerializer#serialized_json` (#91)
|
||||
|
||||
### Fixed
|
||||
- Ensure caching correctly incorporates fieldset information into the cache key to prevent incorrect fieldset caching (#90)
|
||||
|
||||
## [1.7.2] - 2020-05-18
|
||||
### Fixed
|
||||
- Relationship#record_type_for does not assign static record type for polymorphic relationships (#83)
|
||||
|
21
README.md
21
README.md
@ -409,6 +409,25 @@ So for the example above it will call the cache instance like this:
|
||||
Rails.cache.fetch(record, namespace: 'jsonapi-serializer', expires_in: 1.hour) { ... }
|
||||
```
|
||||
|
||||
#### Caching and Sparse Fieldsets
|
||||
|
||||
If caching is enabled and fields are provided to the serializer, the fieldset will be appended to the cache key's namespace.
|
||||
|
||||
For example, given the following serializer definition and instance:
|
||||
```ruby
|
||||
class ActorSerializer
|
||||
include JSONAPI::Serializer
|
||||
|
||||
attributes :first_name, :last_name
|
||||
|
||||
cache_options store: Rails.cache, namespace: 'jsonapi-serializer', expires_in: 1.hour
|
||||
end
|
||||
|
||||
serializer = ActorSerializer.new(actor, { fields: { actor: [:first_name] } })
|
||||
```
|
||||
|
||||
The following cache namespace will be generated: `'jsonapi-serializer-fieldset:first_name'`.
|
||||
|
||||
### Params
|
||||
|
||||
In some cases, attribute values might require more information than what is
|
||||
@ -469,7 +488,7 @@ class MovieSerializer
|
||||
# The director will be serialized only if the :admin key of params is true
|
||||
params && params[:admin] == true
|
||||
}
|
||||
|
||||
|
||||
# Custom attribute `name_year` will only be serialized if both `name` and `year` fields are present
|
||||
attribute :name_year, if: Proc.new { |record|
|
||||
record.name.present? && record.year.present?
|
||||
|
@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'active_support/concern'
|
||||
require 'digest/sha1'
|
||||
|
||||
module FastJsonapi
|
||||
MandatoryField = Class.new(StandardError)
|
||||
@ -66,7 +67,7 @@ module FastJsonapi
|
||||
|
||||
def record_hash(record, fieldset, includes_list, params = {})
|
||||
if cache_store_instance
|
||||
record_hash = cache_store_instance.fetch(record, **cache_store_options) do
|
||||
record_hash = cache_store_instance.fetch(record, **cache_options_with_fieldsets(cache_store_options, fieldset)) do
|
||||
temp_hash = id_hash(id_from_record(record, params), record_type, true)
|
||||
temp_hash[:attributes] = attributes_hash(record, fieldset, params) if attributes_to_serialize.present?
|
||||
temp_hash[:relationships] = {}
|
||||
@ -86,6 +87,32 @@ module FastJsonapi
|
||||
record_hash
|
||||
end
|
||||
|
||||
#
|
||||
# It modifies cache options to include fieldset information in the cache namespace.
|
||||
#
|
||||
# If a fieldset is specified, it modifies the namespace to include the fields from the fieldset.
|
||||
#
|
||||
# If no fieldset is specified, the namespace will be unaltered.
|
||||
#
|
||||
# @param [Hash] options cache options hash
|
||||
# @param [Array, nil] fieldset fieldset array or nil if unspecified
|
||||
#
|
||||
# @return [Hash] processed options hash
|
||||
#
|
||||
def cache_options_with_fieldsets(options, fieldset)
|
||||
return options unless fieldset
|
||||
|
||||
options = options ? options.dup : {}
|
||||
options[:namespace] ||= 'jsonapi-serializer'
|
||||
|
||||
fieldset_key = fieldset.join('_')
|
||||
|
||||
# Use a fixed-length fieldset key if the current length is more than the length of a SHA1 digest
|
||||
fieldset_key = Digest::SHA1.hexdigest(fieldset_key) if fieldset_key.length > 40
|
||||
options[:namespace] = "#{options[:namespace]}-fieldset:#{fieldset_key}"
|
||||
options
|
||||
end
|
||||
|
||||
def id_from_record(record, params)
|
||||
return FastJsonapi.call_proc(record_id, record, params) if record_id.is_a?(Proc)
|
||||
return record.send(record_id) if record_id
|
||||
|
@ -26,4 +26,45 @@ RSpec.describe FastJsonapi::ObjectSerializer do
|
||||
).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with caching and different fieldsets' do
|
||||
context 'when fieldset is provided' do
|
||||
it 'includes the fieldset in the namespace' do
|
||||
expect(cache_store.delete(actor, namespace: 'test')).to be(false)
|
||||
|
||||
Cached::ActorSerializer.new(
|
||||
[actor], fields: { actor: %i[first_name] }
|
||||
).serializable_hash
|
||||
|
||||
# Expect cached keys to match the passed fieldset
|
||||
expect(cache_store.read(actor, namespace: 'test-fieldset:first_name')[:attributes].keys).to eq(%i[first_name])
|
||||
|
||||
Cached::ActorSerializer.new(
|
||||
[actor]
|
||||
).serializable_hash
|
||||
|
||||
# Expect cached keys to match all valid actor fields (no fieldset)
|
||||
expect(cache_store.read(actor, namespace: 'test')[:attributes].keys).to eq(%i[first_name last_name email])
|
||||
expect(cache_store.delete(actor, namespace: 'test')).to be(true)
|
||||
expect(cache_store.delete(actor, namespace: 'test-fieldset:first_name')).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when long fieldset is provided' do
|
||||
let(:actor_keys) { %i[first_name last_name more_fields yet_more_fields so_very_many_fields] }
|
||||
let(:digest_key) { Digest::SHA1.hexdigest(actor_keys.join('_')) }
|
||||
|
||||
it 'includes the hashed fieldset in the namespace' do
|
||||
Cached::ActorSerializer.new(
|
||||
[actor], fields: { actor: actor_keys }
|
||||
).serializable_hash
|
||||
|
||||
expect(cache_store.read(actor, namespace: "test-fieldset:#{digest_key}")[:attributes].keys).to eq(
|
||||
%i[first_name last_name]
|
||||
)
|
||||
|
||||
expect(cache_store.delete(actor, namespace: "test-fieldset:#{digest_key}")).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user