Refactor caching support (#52)
- Very explicit where caching is stored - No `Rails.cache` automagic with possible colliding keys - Rails 5.2+ cache versioning support - Implicit namespace support (by cache instance, e.g. ActiveSupport::Cache::MemoryStore.new(namespace: 'jast_jsonapi') - All cache instance options are supported
This commit is contained in:
parent
3faca2d1e0
commit
843d943e07
24
README.md
24
README.md
@ -362,17 +362,31 @@ was introduced to be able to have precise control this behavior
|
||||
- `false` will always treat input resource as *single object*
|
||||
|
||||
### Caching
|
||||
Requires a `cache_key` method be defined on model:
|
||||
|
||||
To enable caching, use `cache_options store: <cache_store>`:
|
||||
|
||||
```ruby
|
||||
class MovieSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie # optional
|
||||
cache_options enabled: true, cache_length: 12.hours
|
||||
attributes :name, :year
|
||||
|
||||
# use rails cache with a separate namespace and fixed expiry
|
||||
cache_options store: Rails.cache, namespace: 'fast-jsonapi', expires_in: 1.hour
|
||||
end
|
||||
```
|
||||
|
||||
`store` is required can be anything that implements a
|
||||
`#fetch(record, **options, &block)` method:
|
||||
|
||||
- `record` is the record that is currently serialized
|
||||
- `options` is everything that was passed to `cache_options` except `store`, so it can be everyhing the cache store supports
|
||||
- `&block` should be executed to fetch new data if cache is empty
|
||||
|
||||
So for the example above, FastJsonapi will call the cache instance like this:
|
||||
|
||||
```ruby
|
||||
Rails.cache.fetch(record, namespace: 'fast-jsonapi, expires_in: 1.hour) { ... }
|
||||
```
|
||||
|
||||
### Params
|
||||
|
||||
In some cases, attribute values might require more information than what is
|
||||
@ -592,7 +606,7 @@ Option | Purpose | Example
|
||||
set_type | Type name of Object | `set_type :movie`
|
||||
key | Key of Object | `belongs_to :owner, key: :user`
|
||||
set_id | ID of Object | `set_id :owner_id` or `set_id { \|record, params\| params[:admin] ? record.id : "#{record.name.downcase}-#{record.id}" }`
|
||||
cache_options | Hash to enable caching and set cache length | `cache_options enabled: true, cache_length: 12.hours, race_condition_ttl: 10.seconds`
|
||||
cache_options | Hash with store to enable caching and optional further cache options | `cache_options store: ActiveSupport::Cache::MemoryStore.new, expires_in: 5.minutes`
|
||||
id_method_name | Set custom method name to get ID of an object (If block is provided for the relationship, `id_method_name` is invoked on the return value of the block instead of the resource object) | `has_many :locations, id_method_name: :place_ids`
|
||||
object_method_name | Set custom method name to get related objects | `has_many :locations, object_method_name: :places`
|
||||
record_type | Set custom Object Type for a relationship | `belongs_to :owner, record_type: :user`
|
||||
|
@ -129,10 +129,9 @@ module FastJsonapi
|
||||
subclass.cachable_relationships_to_serialize = cachable_relationships_to_serialize.dup if cachable_relationships_to_serialize.present?
|
||||
subclass.uncachable_relationships_to_serialize = uncachable_relationships_to_serialize.dup if uncachable_relationships_to_serialize.present?
|
||||
subclass.transform_method = transform_method
|
||||
subclass.cache_length = cache_length
|
||||
subclass.race_condition_ttl = race_condition_ttl
|
||||
subclass.data_links = data_links.dup if data_links.present?
|
||||
subclass.cached = cached
|
||||
subclass.cache_store_instance = cache_store_instance
|
||||
subclass.cache_store_options = cache_store_options
|
||||
subclass.set_type(subclass.reflected_record_type) if subclass.reflected_record_type
|
||||
subclass.meta_to_serialize = meta_to_serialize
|
||||
subclass.record_id = record_id
|
||||
@ -181,9 +180,32 @@ module FastJsonapi
|
||||
end
|
||||
|
||||
def cache_options(cache_options)
|
||||
self.cached = cache_options[:enabled] || false
|
||||
self.cache_length = cache_options[:cache_length] || 5.minutes
|
||||
self.race_condition_ttl = cache_options[:race_condition_ttl] || 5.seconds
|
||||
# FIXME: remove this if block once deprecated cache_options are not supported anymore
|
||||
if !cache_options.key?(:store)
|
||||
# fall back to old, deprecated behaviour because no store was passed.
|
||||
# we assume the user explicitly wants new behaviour if he passed a
|
||||
# store because this is the new syntax.
|
||||
deprecated_cache_options(cache_options)
|
||||
return
|
||||
end
|
||||
|
||||
self.cache_store_instance = cache_options[:store]
|
||||
self.cache_store_options = cache_options.except(:store)
|
||||
end
|
||||
|
||||
# FIXME: remove this method once deprecated cache_options are not supported anymore
|
||||
def deprecated_cache_options(cache_options)
|
||||
warn('DEPRECATION WARNING: `store:` is a required cache option, we will default to `Rails.cache` for now. See https://github.com/fast-jsonapi/fast_jsonapi#caching for more information.')
|
||||
|
||||
%i[enabled cache_length].select { |key| cache_options.key?(key) }.each do |key|
|
||||
warn("DEPRECATION WARNING: `#{key}` is a deprecated cache option and will have no effect soon. See https://github.com/fast-jsonapi/fast_jsonapi#caching for more information.")
|
||||
end
|
||||
|
||||
self.cache_store_instance = cache_options[:enabled] ? Rails.cache : nil
|
||||
self.cache_store_options = {
|
||||
expires_in: cache_options[:cache_length] || 5.minutes,
|
||||
race_condition_ttl: cache_options[:race_condition_ttl] || 5.seconds
|
||||
}
|
||||
end
|
||||
|
||||
def attributes(*attributes_list, &block)
|
||||
|
@ -17,9 +17,8 @@ module FastJsonapi
|
||||
:transform_method,
|
||||
:record_type,
|
||||
:record_id,
|
||||
:cache_length,
|
||||
:race_condition_ttl,
|
||||
:cached,
|
||||
:cache_store_instance,
|
||||
:cache_store_options,
|
||||
:data_links,
|
||||
:meta_to_serialize
|
||||
end
|
||||
@ -66,8 +65,8 @@ module FastJsonapi
|
||||
end
|
||||
|
||||
def record_hash(record, fieldset, includes_list, params = {})
|
||||
if cached
|
||||
record_hash = Rails.cache.fetch(record.cache_key, expires_in: cache_length, race_condition_ttl: race_condition_ttl) do
|
||||
if cache_store_instance
|
||||
record_hash = cache_store_instance.fetch(record, **cache_store_options) 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] = {}
|
||||
|
@ -4,12 +4,6 @@ describe FastJsonapi::ObjectSerializer do
|
||||
include_context 'movie class'
|
||||
|
||||
context 'when caching has_many' do
|
||||
before(:each) do
|
||||
rails = OpenStruct.new
|
||||
rails.cache = ActiveSupport::Cache::MemoryStore.new
|
||||
stub_const('Rails', rails)
|
||||
end
|
||||
|
||||
it 'returns correct hash when serializable_hash is called' do
|
||||
options = {}
|
||||
options[:meta] = { total: 2 }
|
||||
@ -70,4 +64,44 @@ describe FastJsonapi::ObjectSerializer do
|
||||
expect(serializable_hash[:data][:relationships][:actors][:data].length).to eq previous_actors.length
|
||||
end
|
||||
end
|
||||
|
||||
# FIXME: remove this if block once deprecated cache_options are not supported anymore
|
||||
context 'when using deprecated cache options' do
|
||||
let(:deprecated_caching_movie_serializer_class) do
|
||||
rails = OpenStruct.new
|
||||
rails.cache = ActiveSupport::Cache::MemoryStore.new
|
||||
stub_const('Rails', rails)
|
||||
|
||||
Class.new do
|
||||
def self.name
|
||||
'DeprecatedCachingMovieSerializer'
|
||||
end
|
||||
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
attributes :name, :release_year
|
||||
has_many :actors
|
||||
belongs_to :owner, record_type: :user
|
||||
belongs_to :movie_type
|
||||
|
||||
cache_options enabled: true
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses cached values for the record' do
|
||||
previous_name = movie.name
|
||||
previous_actors = movie.actors
|
||||
deprecated_caching_movie_serializer_class.new(movie).serializable_hash
|
||||
|
||||
movie.name = 'should not match'
|
||||
allow(movie).to receive(:actor_ids).and_return([99])
|
||||
|
||||
expect(previous_name).not_to eq(movie.name)
|
||||
expect(previous_actors).not_to eq(movie.actors)
|
||||
serializable_hash = deprecated_caching_movie_serializer_class.new(movie).serializable_hash
|
||||
|
||||
expect(serializable_hash[:data][:attributes][:name]).to eq(previous_name)
|
||||
expect(serializable_hash[:data][:relationships][:actors][:data].length).to eq movie.actors.length
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -222,7 +222,7 @@ RSpec.shared_context 'movie class' do
|
||||
belongs_to :owner, record_type: :user
|
||||
belongs_to :movie_type
|
||||
|
||||
cache_options enabled: true
|
||||
cache_options store: ActiveSupport::Cache::MemoryStore.new, expires_in: 5.minutes
|
||||
end
|
||||
|
||||
class CachingMovieWithHasManySerializer
|
||||
@ -233,7 +233,7 @@ RSpec.shared_context 'movie class' do
|
||||
belongs_to :owner, record_type: :user
|
||||
belongs_to :movie_type
|
||||
|
||||
cache_options enabled: true
|
||||
cache_options store: ActiveSupport::Cache::MemoryStore.new, namespace: 'fast-jsonapi'
|
||||
end
|
||||
|
||||
class ActorSerializer
|
||||
|
Loading…
x
Reference in New Issue
Block a user