Skip to content

Commit 637956d

Browse files
Serialization caching
1 parent 22ba79b commit 637956d

3 files changed

Lines changed: 40 additions & 6 deletions

File tree

README.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1920,7 +1920,7 @@ end
19201920
Then, on each Resource you want to cache, call the `caching` method:
19211921

19221922
```ruby
1923-
class PostResource << JSONAPI::Resource
1923+
class PostResource < JSONAPI::Resource
19241924
caching
19251925
end
19261926
```
@@ -1933,7 +1933,8 @@ The default Rails timestamps handle this pretty well, and the default cache key
19331933
You can use an alternate field (which you are then responsible for updating) by calling the `cache_field` method:
19341934
19351935
```ruby
1936-
class PostResource << JSONAPI::Resource
1936+
class PostResource < JSONAPI::Resource
1937+
caching
19371938
cache_field :change_counter
19381939
19391940
before_save do
@@ -1951,10 +1952,12 @@ end
19511952
```
19521953
19531954
If context affects the content of the serialized result, you must define a class method `attribute_caching_context` on that Resource, which should return a different value for contexts that produce different results. In particular, if the `meta` or `fetchable_fields` methods, or any method providing the actual content of an attribute, changes depending on context, then you must provide `attribute_caching_context`. The actual value it
1954-
returns isn't important, except that the value must be different if any relevant aspect of the context is different.
1955+
returns isn't important, what matters is that the value must be different if any relevant part of the context is different.
19551956

19561957
```ruby
1957-
class PostResource << JSONAPI::Resource
1958+
class PostResource < JSONAPI::Resource
1959+
caching
1960+
19581961
attributes :title, :body, :secret_field
19591962
19601963
def fetchable_fields
@@ -1984,14 +1987,14 @@ end
19841987
* Models for cached Resources must update a cache key field whenever their data changes. However, if you bypass Rails and e.g. alter the database row directly without changing the `updated_at` field, the cached entry for that resource will be inaccurate. Also, `updated_at` provides a narrow race condition window; if a resource is updated twice in the same second, it's possible that only the first update will be cached. If you're concerned about this, you will need to find a way to make sure your models' cache fields change on every update, e.g. by using a unique random value or a monotonic clock.
19851988
* If an attribute's value is affected by related resources, e.g. the `spoken_languages` example above, then changes to the related resource must also touch the cache field on the resource that uses it. The `belongs_to` relation in ActiveRecord provides a `:touch` option for this purpose.
19861989
* JR does not actively clean the cache, so you must use an ActiveSupport cache that automatically expires old entries, or you will leak resources. The MemoryCache built in to Rails does this by default, but other caches will have to be configured with an `:expires_in` option and/or a cache-specific clearing mechanism.
1987-
* Similarly, if you make a substantial code change that affects a lot of serialized representations (i.e. changing the way an attribute is shown), you'll have to clear out all relevant cache entries yourself. You do not have to do this after merely adding or removing attributes; only changes that affect the actual content of attributes require manual cache clearing. The simplest way to do this is to run `JSONAPI.configuration.resource_cache.clear` from the console.
1990+
* Similarly, if you make a substantial code change that affects a lot of serialized representations (i.e. changing the way an attribute is shown), you'll have to clear out all relevant cache entries yourself. The simplest way to do this is to run `JSONAPI.configuration.resource_cache.clear` from the console. You do not have to do this after merely adding or removing attributes; only changes that affect the actual content of attributes require manual cache clearing.
19881991
* If resource caching is enabled at all, then custom relationship methods on any resource might not always be used, even resources that are not cached. For example, if you manually define a `comments` method or `records_for_comments` method on a Resource that `has_many :comments`, you cannot expect it to be used when caching is enabled, even if you never call `caching` on that particular Resource. Instead, you should use relationship name lambdas.
19891992
* The above also applies to custom `find` or `find_by_key` methods. Instead, if you are using resource caching anywhere in your app, try overriding the `find_records` method to return an appropriate `ActiveRecord::Relation`.
19901993
* Caching relies on ActiveRecord features; you cannot enable caching on resources based on non-AR models, e.g. PORO objects or singleton resources.
19911994
* If you write a custom `ResourceSerializer` which takes new options, then you must define `config_description` to include those options if they might impact the serialized value:
19921995
19931996
```ruby
1994-
class MySerializer << JSONAPI::ResourceSerializer
1997+
class MySerializer < JSONAPI::ResourceSerializer
19951998
def initialize(primary_resource_klass, options = {})
19961999
@my_special_option = options.delete(:my_special_option)
19972000
super

lib/jsonapi/request_parser.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ def add_show_related_resources_operation(relationship_type)
339339
source_klass: @source_klass,
340340
source_id: @source_id,
341341
filters: @source_klass.verify_filters(@filters, @context),
342+
include_directives: @include_directives,
342343
sort_criteria: @sort_criteria,
343344
paginator: @paginator,
344345
fields: @fields,

lib/jsonapi/resource.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,36 @@ def cached_resources_for(records, serializer, options)
10711071
resources.values
10721072
end
10731073

1074+
def find_records(filters, options = {})
1075+
context = options[:context]
1076+
1077+
records = filter_records(filters, options)
1078+
1079+
sort_criteria = options.fetch(:sort_criteria) { [] }
1080+
order_options = construct_order_options(sort_criteria)
1081+
records = sort_records(records, order_options, context)
1082+
1083+
records = apply_pagination(records, options[:paginator], order_options)
1084+
1085+
records
1086+
end
1087+
1088+
def cached_resources_for(records, serializer, options)
1089+
if records.is_a?(Array) && records.all?{|rec| rec.is_a?(JSONAPI::Resource)}
1090+
resources = records.map{|r| [r.id, r] }.to_h
1091+
elsif self.caching?
1092+
t = _model_class.arel_table
1093+
cache_ids = pluck_arel_attributes(records, t[_primary_key], t[_cache_field])
1094+
resources = CachedResourceFragment.fetch_fragments(self, serializer, options[:context], cache_ids)
1095+
else
1096+
resources = resources_for(records, options).map{|r| [r.id, r] }.to_h
1097+
end
1098+
1099+
preload_included_fragments(resources, records, serializer, options)
1100+
1101+
resources.values
1102+
end
1103+
10741104
def check_reserved_resource_name(type, name)
10751105
if [:ids, :types, :hrefs, :links].include?(type)
10761106
warn "[NAME COLLISION] `#{name}` is a reserved resource name."

0 commit comments

Comments
 (0)