Skip to content

Commit c3c69a6

Browse files
committed
Separate enabling of caching and setting the cache store
1 parent 9953d7a commit c3c69a6

File tree

4 files changed

+248
-8
lines changed

4 files changed

+248
-8
lines changed

lib/active_model/serializer/caching.rb

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module Caching
55

66
included do
77
with_options instance_writer: false, instance_reader: false do |serializer|
8-
serializer.class_attribute :_cache # @api private : the cache object
8+
serializer.class_attribute :_cache # @api private : the cache store
99
serializer.class_attribute :_fragmented # @api private : @see ::fragmented
1010
serializer.class_attribute :_cache_key # @api private : when present, is first item in cache_key
1111
serializer.class_attribute :_cache_only # @api private : when fragment caching, whitelists cached_attributes. Cannot combine with except
@@ -71,6 +71,7 @@ def fragmented(serializer)
7171
# when Rails.configuration.action_controller.perform_caching
7272
#
7373
# @params options [Hash] with valid keys:
74+
# cache_store : @see ::_cache
7475
# key : @see ::_cache_key
7576
# only : @see ::_cache_only
7677
# except : @see ::_cache_except
@@ -88,16 +89,74 @@ def fragmented(serializer)
8889
# @todo require less code comments. See
8990
# https://github.com/rails-api/active_model_serializers/pull/1249#issuecomment-146567837
9091
def cache(options = {})
91-
self._cache = ActiveModelSerializers.config.cache_store if ActiveModelSerializers.config.perform_caching
92-
serializer = self
93-
ActiveSupport.on_load(:action_controller) do
94-
serializer._cache = ActiveModelSerializers.config.cache_store if ActiveModelSerializers.config.perform_caching
95-
end
92+
self._cache =
93+
options.delete(:cache_store) ||
94+
ActiveModelSerializers.config.cache_store ||
95+
ActiveSupport::Cache.lookup_store(:null_store)
9696
self._cache_key = options.delete(:key)
9797
self._cache_only = options.delete(:only)
9898
self._cache_except = options.delete(:except)
9999
self._cache_options = options.empty? ? nil : options
100100
end
101+
102+
# @return [true, false]
103+
# We're using class variables here because it is a class attribute
104+
# that is globally true for the `ActiveModel::Serializer` class; i.e. neither per subclass nor inherited.
105+
#
106+
# We're not using a class_attribute because of the special behavior in
107+
# `perform_caching` setting itself to `ActiveModelSerializers.config.perform_caching`
108+
# when first called if it wasn't first set.
109+
#
110+
# This is to allow us to have a global config that can be set any time before
111+
# `perform_caching` is called.
112+
#
113+
# One downside of this, is that subsequent setting of the global config will not change
114+
# `ActiveModel::Serializer.perform_caching`, but that should be an edge case that
115+
# is easily handled.
116+
#
117+
# If you, reading this, can figure out how to have ActiveModel::Serializer always delegate
118+
# `perform_caching` and `perform_caching=` to the global config, that would make a nice PR.
119+
# rubocop:disable Style/ClassVars
120+
def perform_caching
121+
return @@perform_caching if defined?(@@perform_caching)
122+
self.perform_caching = ActiveModelSerializers.config.perform_caching
123+
end
124+
alias perform_caching? perform_caching
125+
126+
# @param [true, false]
127+
def perform_caching=(perform_caching)
128+
@@perform_caching = perform_caching
129+
end
130+
# rubocop:enable Style/ClassVars
131+
132+
# The canonical method for getting the cache store for the serializer.
133+
#
134+
# @return [nil] when _cache is not set (i.e. when `cache` has not been called)
135+
# @return [._cache] when _cache is not the NullStore
136+
# @return [ActiveModelSerializers.config.cache_store] when _cache is the NullStore.
137+
# This is so we can use `cache` being called to mean the serializer should be cached
138+
# even if ActiveModelSerializers.config.cache_store has not yet been set.
139+
# That means that when _cache is the NullStore and ActiveModelSerializers.config.cache_store
140+
# is configured, `cache_store` becomes `ActiveModelSerializers.config.cache_store`.
141+
# @return [nil] when _cache is the NullStore and ActiveModelSerializers.config.cache_store is nil.
142+
def cache_store
143+
return nil if _cache.nil?
144+
return _cache if _cache.class != ActiveSupport::Cache::NullStore
145+
if ActiveModelSerializers.config.cache_store
146+
self._cache = ActiveModelSerializers.config.cache_store
147+
else
148+
nil
149+
end
150+
end
151+
152+
def cache_enabled?
153+
perform_caching? && cache_store && !_cache_only && !_cache_except
154+
end
155+
156+
def fragment_cache_enabled?
157+
perform_caching? && cache_store &&
158+
(_cache_only && !_cache_except || !_cache_only && _cache_except)
159+
end
101160
end
102161
end
103162
end

lib/active_model_serializers/cached_serializer.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ def cache_check(adapter_instance)
1818
end
1919

2020
def cached?
21-
@klass._cache && !@klass._cache_only && !@klass._cache_except
21+
@klass.cache_enabled?
2222
end
2323

2424
def fragment_cached?
25-
@klass._cache && (@klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except)
25+
@klass.fragment_cache_enabled?
2626
end
2727

2828
def cache_key

test/cache_test.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@ def setup
3434
@blog_serializer = BlogSerializer.new(@blog)
3535
end
3636

37+
def test_explicit_cache_store
38+
default_store = Class.new(ActiveModel::Serializer) do
39+
cache
40+
end
41+
explicit_store = Class.new(ActiveModel::Serializer) do
42+
cache cache_store: ActiveSupport::Cache::FileStore
43+
end
44+
45+
assert ActiveSupport::Cache::MemoryStore, ActiveModelSerializers.config.cache_store
46+
assert ActiveSupport::Cache::MemoryStore, default_store.cache_store
47+
assert ActiveSupport::Cache::FileStore, explicit_store.cache_store
48+
end
49+
3750
def test_inherited_cache_configuration
3851
inherited_serializer = Class.new(PostSerializer)
3952

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# Execute this test in isolation
2+
require 'support/isolated_unit'
3+
4+
class CachingConfigurationTest < ActiveSupport::TestCase
5+
include ActiveSupport::Testing::Isolation
6+
7+
setup do
8+
require 'rails'
9+
# AMS needs to be required before Rails.application is initialized for
10+
# Railtie's to fire in Rails.application.initialize!
11+
# (and make_basic_app initializes the app)
12+
require 'active_model_serializers'
13+
# Create serializers before Rails.application.initialize!
14+
# To ensure we're testing that the cache settings depend on
15+
# the Railtie firing, not on the ActionController being loaded.
16+
create_serializers
17+
end
18+
19+
def create_serializers
20+
@cached_serializer = Class.new(ActiveModel::Serializer) do
21+
cache skip_digest: true
22+
attributes :id, :name, :title
23+
end
24+
@fragment_cached_serializer = Class.new(ActiveModel::Serializer) do
25+
cache only: :id
26+
attributes :id, :name, :title
27+
end
28+
@non_cached_serializer = Class.new(ActiveModel::Serializer) do
29+
attributes :id, :name, :title
30+
end
31+
end
32+
33+
class PerformCachingTrue < CachingConfigurationTest
34+
setup do
35+
# Let's make that Rails app and initialize it!
36+
make_basic_app do |app|
37+
app.config.action_controller.perform_caching = true
38+
app.config.action_controller.cache_store = ActiveSupport::Cache.lookup_store(:memory_store)
39+
end
40+
end
41+
42+
test 'it sets perform_caching to true on AMS.config and serializers' do
43+
assert Rails.configuration.action_controller.perform_caching
44+
assert ActiveModelSerializers.config.perform_caching
45+
assert ActiveModel::Serializer.perform_caching?
46+
assert @cached_serializer.perform_caching?
47+
assert @non_cached_serializer.perform_caching?
48+
assert @fragment_cached_serializer.perform_caching?
49+
end
50+
51+
test 'it sets the AMS.config.cache_store to the controller cache_store' do
52+
assert_equal controller_cache_store, ActiveSupport::Cache::MemoryStore
53+
assert_equal controller_cache_store, ActiveModelSerializers.config.cache_store.class
54+
end
55+
56+
test 'it sets the cached serializer cache_store to the ActionController::Base.cache_store' do
57+
assert_equal ActiveSupport::Cache::NullStore, @cached_serializer._cache.class
58+
assert_equal controller_cache_store, @cached_serializer.cache_store.class
59+
assert_equal ActiveSupport::Cache::MemoryStore, @cached_serializer._cache.class
60+
end
61+
62+
test 'the cached serializer has cache_enabled?' do
63+
assert @cached_serializer.cache_enabled?
64+
end
65+
66+
test 'the cached serializer does not have fragment_cache_enabled?' do
67+
refute @cached_serializer.fragment_cache_enabled?
68+
end
69+
70+
test 'the non-cached serializer cache_store is nil' do
71+
assert_equal nil, @non_cached_serializer._cache
72+
assert_equal nil, @non_cached_serializer.cache_store
73+
assert_equal nil, @non_cached_serializer._cache
74+
end
75+
76+
test 'the non-cached serializer does not have cache_enabled?' do
77+
refute @non_cached_serializer.cache_enabled?
78+
end
79+
80+
test 'the non-cached serializer does not have fragment_cache_enabled?' do
81+
refute @non_cached_serializer.fragment_cache_enabled?
82+
end
83+
84+
test 'it sets the fragment cached serializer cache_store to the ActionController::Base.cache_store' do
85+
assert_equal ActiveSupport::Cache::NullStore, @fragment_cached_serializer._cache.class
86+
assert_equal controller_cache_store, @fragment_cached_serializer.cache_store.class
87+
assert_equal ActiveSupport::Cache::MemoryStore, @fragment_cached_serializer._cache.class
88+
end
89+
90+
test 'the fragment cached serializer does not have cache_enabled?' do
91+
refute @fragment_cached_serializer.cache_enabled?
92+
end
93+
94+
test 'the fragment cached serializer has fragment_cache_enabled?' do
95+
assert @fragment_cached_serializer.fragment_cache_enabled?
96+
end
97+
end
98+
99+
class PerformCachingFalse < CachingConfigurationTest
100+
setup do
101+
# Let's make that Rails app and initialize it!
102+
make_basic_app do |app|
103+
app.config.action_controller.perform_caching = false
104+
app.config.action_controller.cache_store = ActiveSupport::Cache.lookup_store(:memory_store)
105+
end
106+
end
107+
108+
test 'it sets perform_caching to false on AMS.config and serializers' do
109+
refute Rails.configuration.action_controller.perform_caching
110+
refute ActiveModelSerializers.config.perform_caching
111+
refute ActiveModel::Serializer.perform_caching?
112+
refute @cached_serializer.perform_caching?
113+
refute @non_cached_serializer.perform_caching?
114+
refute @fragment_cached_serializer.perform_caching?
115+
end
116+
117+
test 'it sets the AMS.config.cache_store to the controller cache_store' do
118+
assert_equal controller_cache_store, ActiveSupport::Cache::MemoryStore
119+
assert_equal controller_cache_store, ActiveModelSerializers.config.cache_store.class
120+
end
121+
122+
test 'it sets the cached serializer cache_store to the ActionController::Base.cache_store' do
123+
assert_equal ActiveSupport::Cache::NullStore, @cached_serializer._cache.class
124+
assert_equal controller_cache_store, @cached_serializer.cache_store.class
125+
assert_equal ActiveSupport::Cache::MemoryStore, @cached_serializer._cache.class
126+
end
127+
128+
test 'the cached serializer does not have cache_enabled?' do
129+
refute @cached_serializer.cache_enabled?
130+
end
131+
132+
test 'the cached serializer does not have fragment_cache_enabled?' do
133+
refute @cached_serializer.fragment_cache_enabled?
134+
end
135+
136+
test 'the non-cached serializer cache_store is nil' do
137+
assert_equal nil, @non_cached_serializer._cache
138+
assert_equal nil, @non_cached_serializer.cache_store
139+
assert_equal nil, @non_cached_serializer._cache
140+
end
141+
142+
test 'the non-cached serializer does not have cache_enabled?' do
143+
refute @non_cached_serializer.cache_enabled?
144+
end
145+
146+
test 'the non-cached serializer does not have fragment_cache_enabled?' do
147+
refute @non_cached_serializer.fragment_cache_enabled?
148+
end
149+
150+
test 'it sets the fragment cached serializer cache_store to the ActionController::Base.cache_store' do
151+
assert_equal ActiveSupport::Cache::NullStore, @fragment_cached_serializer._cache.class
152+
assert_equal controller_cache_store, @fragment_cached_serializer.cache_store.class
153+
assert_equal ActiveSupport::Cache::MemoryStore, @fragment_cached_serializer._cache.class
154+
end
155+
156+
test 'the fragment cached serializer does not have cache_enabled?' do
157+
refute @fragment_cached_serializer.cache_enabled?
158+
end
159+
160+
test 'the fragment cached serializer does not have fragment_cache_enabled?' do
161+
refute @fragment_cached_serializer.fragment_cache_enabled?
162+
end
163+
end
164+
165+
def controller_cache_store
166+
ActionController::Base.cache_store.class
167+
end
168+
end

0 commit comments

Comments
 (0)