Skip to content

Commit 4ba4c29

Browse files
committed
Prefer object.cache_key when available.
1 parent ab6bd60 commit 4ba4c29

File tree

6 files changed

+55
-76
lines changed

6 files changed

+55
-76
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
Breaking changes:
44

55
Features:
6+
- [#1642](https://github.com/rails-api/active_model_serializers/pull/1642) Prefer object.cache_key over the generated
7+
cache key. (@bf4 via #1346 by @kevintyll)
68
- [#1637](https://github.com/rails-api/active_model_serializers/pull/1637) Make references to 'ActionController::Base.cache_store' explicit
79
in order to avoid issues when application controllers inherit from 'ActionController::API'. (@ncuesta)
810
- [#1633](https://github.com/rails-api/active_model_serializers/pull/1633) Yield 'serializer' to serializer association blocks. (@bf4)

lib/active_model/serializer/caching.rb

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,6 @@ def digest_caller_file(caller_line)
5858
''.freeze
5959
end
6060

61-
def _skip_digest?
62-
_cache_options && _cache_options[:skip_digest]
63-
end
64-
6561
# @api private
6662
# Used by FragmentCache on the CachedSerializer
6763
# to call attribute methods on the fragmented cached serializer.
@@ -146,18 +142,6 @@ def fragment_cache_enabled?
146142
(_cache_only && !_cache_except || !_cache_only && _cache_except)
147143
end
148144
end
149-
150-
# Use object's cache_key if available, else derive a key from the object
151-
# Pass the `key` option to the `cache` declaration or override this method to customize the cache key
152-
def cache_key
153-
if object.respond_to?(:cache_key)
154-
object.cache_key
155-
else
156-
object_time_safe = object.updated_at
157-
object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime)
158-
"#{self.class._cache_key}/#{object.id}-#{object_time_safe}"
159-
end
160-
end
161145
end
162146
end
163147
end

lib/active_model_serializers/adapter/base.rb

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,13 @@ def self.inherited(subclass)
88
ActiveModelSerializers::Adapter.register(subclass)
99
end
1010

11-
def self.name
12-
to_s.demodulize
13-
end
14-
1511
attr_reader :serializer, :instance_options
1612

1713
def initialize(serializer, options = {})
1814
@serializer = serializer
1915
@instance_options = options
2016
end
2117

22-
def name
23-
self.class.name
24-
end
25-
2618
def serializable_hash(_options = nil)
2719
fail NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.'
2820
end

lib/active_model_serializers/cached_serializer.rb

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@ class CachedSerializer
44

55
def initialize(serializer)
66
@cached_serializer = serializer
7-
return unless cached? && !@cached_serializer.object.respond_to?(:cache_key) && @klass._cache_key.blank?
8-
fail(UndefinedCacheKey, "#{@cached_serializer.object} must define #cache_key, or the cache_key option must be passed into cache on #{@cached_serializer}")
7+
@klass = @cached_serializer.class
98
end
109

1110
def cache_check(adapter_instance)
1211
if cached?
13-
@klass._cache.fetch(cache_key(adapter_instance), @klass._cache_options) do
12+
@klass._cache.fetch(cache_key, @klass._cache_options) do
1413
yield
1514
end
1615
elsif fragment_cached?
@@ -28,16 +27,29 @@ def fragment_cached?
2827
@klass.fragment_cache_enabled?
2928
end
3029

31-
def cache_key(adapter_instance)
30+
def cache_key
3231
return @cache_key if defined?(@cache_key)
3332

3433
parts = []
35-
parts << @cached_serializer.cache_key
36-
parts << adapter_instance.name.underscore
37-
parts << @klass._cache_digest unless @klass._skip_digest?
34+
parts << object_cache_key
35+
parts << @klass._cache_digest unless @klass._cache_options && @klass._cache_options[:skip_digest]
3836
@cache_key = parts.join('/')
3937
end
4038

39+
# Use object's cache_key if available, else derive a key from the object
40+
# Pass the `key` option to the `cache` declaration or override this method to customize the cache key
41+
def object_cache_key
42+
if @cached_serializer.object.respond_to?(:cache_key)
43+
@cached_serializer.object.cache_key
44+
elsif (cache_key = (@klass._cache_key || @klass._cache_options[:key]))
45+
object_time_safe = @cached_serializer.object.updated_at
46+
object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime)
47+
"#{cache_key}/#{@cached_serializer.object.id}-#{object_time_safe}"
48+
else
49+
fail UndefinedCacheKey, "#{@cached_serializer.object.class} must define #cache_key, or the 'key:' option must be passed into '#{@klass}.cache'"
50+
end
51+
end
52+
4153
# find all cache_key for the collection_serializer
4254
# @param collection_serializer
4355
# @param include_tree

test/cache_test.rb

Lines changed: 31 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -96,26 +96,27 @@ def test_cache_key_definition
9696
assert_equal(nil, @comment_serializer.class._cache_key)
9797
end
9898

99-
100-
def test_error_is_raised_if_cache_key_is_not_defined_on_object_or_passed_as_cache_option
101-
article = Article.new(title: 'Must Read')
102-
assert_raises ActiveModel::Serializer::Adapter::CachedSerializer::UndefinedCacheKey do
103-
render_object_with_cache(article)
104-
end
105-
end
106-
10799
def test_cache_key_interpolation_with_updated_at_when_cache_key_is_not_defined_on_object
108100
uncached_author = UncachedAuthor.new(name: 'Joao M. D. Moura')
109101
uncached_author_serializer = AuthorSerializer.new(uncached_author)
110102

111103
render_object_with_cache(uncached_author)
112-
key = cache_key_with_adapter("#{uncached_author_serializer.class._cache_key}/#{uncached_author_serializer.object.id}-#{uncached_author_serializer.object.updated_at.strftime("%Y%m%d%H%M%S%9N")}")
113-
assert_equal(uncached_author_serializer.attributes.to_json, ActionController::Base.cache_store.fetch(key).to_json)
104+
key = "#{uncached_author_serializer.class._cache_key}/#{uncached_author_serializer.object.id}-#{uncached_author_serializer.object.updated_at.strftime("%Y%m%d%H%M%S%9N")}"
105+
assert_equal(uncached_author_serializer.attributes.to_json, cache_store.fetch(key).to_json)
114106
end
115107

116108
def test_default_cache_key_fallback
117109
render_object_with_cache(@comment)
118-
assert_equal(@comment_serializer.attributes.to_json, ActionController::Base.cache_store.fetch(cache_key_with_adapter(@comment.cache_key)).to_json)
110+
key = @comment.cache_key
111+
assert_equal(@comment_serializer.attributes.to_json, cache_store.fetch(key).to_json)
112+
end
113+
114+
def test_error_is_raised_if_cache_key_is_not_defined_on_object_or_passed_as_cache_option
115+
article = Article.new(title: 'Must Read')
116+
e = assert_raises ActiveModelSerializers::CachedSerializer::UndefinedCacheKey do
117+
render_object_with_cache(article)
118+
end
119+
assert_match(/ActiveModelSerializers::CacheTest::Article must define #cache_key, or the 'key:' option must be passed into 'CachedActiveModelSerializers_CacheTest_ArticleSerializer.cache'/, e.message)
119120
end
120121

121122
def test_cache_options_definition
@@ -137,8 +138,10 @@ def test_associations_separately_cache
137138
Timecop.freeze(Time.current) do
138139
render_object_with_cache(@post)
139140

140-
assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch(cache_key_with_adapter(@post.cache_key)))
141-
assert_equal(@comment_serializer.attributes, ActionController::Base.cache_store.fetch(cache_key_with_adapter(@comment.cache_key)))
141+
key = @post.cache_key
142+
assert_equal(@post_serializer.attributes, cache_store.fetch(key))
143+
key = @comment.cache_key
144+
assert_equal(@comment_serializer.attributes, cache_store.fetch(key))
142145
end
143146
end
144147

@@ -148,9 +151,10 @@ def test_associations_cache_when_updated
148151
render_object_with_cache(@post)
149152

150153
# Check if it cached the objects separately
151-
assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch(cache_key_with_adapter(@post.cache_key)))
152-
assert_equal(@comment_serializer.attributes, ActionController::Base.cache_store.fetch(cache_key_with_adapter(@comment.cache_key)))
153-
154+
key = @post.cache_key
155+
assert_equal(@post_serializer.attributes, cache_store.fetch(key))
156+
key = @comment.cache_key
157+
assert_equal(@comment_serializer.attributes, cache_store.fetch(key))
154158

155159
# Simulating update on comments relationship with Post
156160
new_comment = Comment.new(id: 2567, body: 'ZOMG A NEW COMMENT')
@@ -161,8 +165,10 @@ def test_associations_cache_when_updated
161165
render_object_with_cache(@post)
162166

163167
# Check if the the new comment was cached
164-
assert_equal(new_comment_serializer.attributes, ActionController::Base.cache_store.fetch(cache_key_with_adapter(new_comment.cache_key)))
165-
assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch(cache_key_with_adapter(@post.cache_key)))
168+
key = new_comment.cache_key
169+
assert_equal(new_comment_serializer.attributes, cache_store.fetch(key))
170+
key = @post.cache_key
171+
assert_equal(@post_serializer.attributes, cache_store.fetch(key))
166172
end
167173
end
168174

@@ -177,7 +183,7 @@ def test_fragment_fetch_with_virtual_associations
177183
hash = render_object_with_cache(@location)
178184

179185
assert_equal(hash, expected_result)
180-
assert_equal({ place: 'Nowhere' }, ActionController::Base.cache_store.fetch(cache_key_with_adapter(@location.cache_key)))
186+
assert_equal({ place: 'Nowhere' }, cache_store.fetch(@location.cache_key))
181187
end
182188

183189
def test_fragment_cache_with_inheritance
@@ -188,18 +194,14 @@ def test_fragment_cache_with_inheritance
188194
refute_includes(base.keys, :special_attribute)
189195
end
190196

191-
def test_uses_adapter_in_cache_key
192-
render_object_with_cache(@post)
193-
assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch("#{@post.cache_key}/#{adapter.class.to_s.demodulize.underscore}"))
194-
end
195-
196197
def test_uses_file_digest_in_cache_key
197198
render_object_with_cache(@blog)
198-
assert_equal(@blog_serializer.attributes, ActionController::Base.cache_store.fetch("#{cache_key_with_adapter(@blog.cache_key)}/#{@blog.class::FILE_DIGEST}"))
199+
key = "#{@blog.cache_key}/#{::Model::FILE_DIGEST}"
200+
assert_equal(@blog_serializer.attributes, cache_store.fetch(key))
199201
end
200202

201203
def test_cache_digest_definition
202-
assert_equal(FILE_DIGEST, @post_serializer.class._cache_digest)
204+
assert_equal(::Model::FILE_DIGEST, @post_serializer.class._cache_digest)
203205
end
204206

205207
def test_object_cache_keys
@@ -211,7 +213,7 @@ def test_object_cache_keys
211213
assert_equal actual.size, 3
212214
assert actual.any? { |key| key == 'comment/1' }
213215
assert actual.any? { |key| key =~ %r{post/post-\d+} }
214-
assert actual.any? { |key| key =~ %r{writer/author-\d+} }
216+
assert actual.any? { |key| key =~ %r{author/author-\d+} }
215217
end
216218

217219
def test_cached_attributes
@@ -228,7 +230,7 @@ def test_cached_attributes
228230
assert_equal cached_attributes[@comment.post.cache_key], Post.new(id: 'post', title: 'New Post', body: 'Body').attributes
229231

230232
writer = @comment.post.blog.writer
231-
writer_cache_key = "writer/#{writer.id}-#{writer.updated_at.strftime("%Y%m%d%H%M%S%9N")}"
233+
writer_cache_key = writer.cache_key
232234

233235
assert_equal cached_attributes[writer_cache_key], Author.new(id: 'author', name: 'Joao M. D. Moura').attributes
234236
end
@@ -286,16 +288,7 @@ def test_warn_on_serializer_not_defined_in_file
286288
private
287289

288290
def render_object_with_cache(obj, options = {})
289-
@serializable_resource = serializable(obj, options).serializable_hash
290-
@serializable_resource.serializable_hash
291-
end
292-
293-
def adapter
294-
@serializable_resource.adapter
295-
end
296-
297-
def cache_key_with_adapter(key)
298-
"#{key}/#{adapter.name.underscore}"
291+
serializable(obj, options).serializable_hash
299292
end
300293

301294
def cache_store

test/fixtures/poro.rb

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
verbose = $VERBOSE
22
$VERBOSE = nil
33
class Model < ActiveModelSerializers::Model
4+
FILE_DIGEST = Digest::MD5.hexdigest(File.open(__FILE__).read)
5+
46
### Helper methods, not required to be serializable
57

68
# Convenience when not adding @attributes readers and writers
@@ -52,13 +54,7 @@ class ProfilePreviewSerializer < ActiveModel::Serializer
5254
Like = Class.new(Model)
5355
Author = Class.new(Model)
5456
Bio = Class.new(Model)
55-
Blog = Class.new(Model) do
56-
FILE_DIGEST = Digest::MD5.hexdigest(File.open(__FILE__).read)
57-
58-
def digest
59-
FILE_DIGEST
60-
end
61-
end
57+
Blog = Class.new(Model)
6258
Role = Class.new(Model)
6359
User = Class.new(Model)
6460
Location = Class.new(Model)

0 commit comments

Comments
 (0)