-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FIX] The cache store needs to be the actually store, not e.g. :memory_store #1480
Conversation
Discovered in https://github.com/rails-api/active_model_serializers/pull/1412/files#diff-8c03da6a006ecb5ab61b873ef8f5cba3R76 when I got |
Looks like the right approach to me. ActiveModelSerializers.config.cache_store = Rails.cache I assume. What do you think?. |
@groyoh could be, but I'd need to check the load order of the config and whether there's any chance at init Rails.cache isn't ActionController::Base.cache |
cc @dgynn |
Using You could also move the code into the If the main goal is to stay in sync with the way that ActionController is configured, it really would seem best to always delegate to ActionController::Base.cache rather than having a separate config and reference. |
What are the advantages over the different options?
In my personal projects, I always use APICache https://github.com/NullVoxPopuli/api_cache I feel like this AMS config option should be documented (just at a minimum, what methods the store needs to implement) |
@NullVoxPopuli @groyoh @dgynn I just updated this PR with a little code and lot in the PR description of what I've learned. It was actually a little more involved than I thought |
assert_equal(new_comment_serializer.attributes, ActionController::Base.cache_store.fetch(new_comment.cache_key)) | ||
assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch(@post.cache_key)) | ||
assert_equal(new_comment_serializer.attributes, cached_serialization(new_comment_serializer)) | ||
assert_equal(@post_serializer.attributes, cached_serialization(@post_serializer)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the test that is failing w memory store
Found the culprit https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/adapter/attributes.rb#L58. Here we modify in-place the value that is returned from the memory_store and merge in the relationships. Therefore the value within the cache is modified in place. My guess is that the file_store returns a copy/dup of the value whereas the memory_store simply returns the reference to the same object. |
Status quo in test app: In Rails ActionController::Base.cache_store = :memory_store and then AMS railtie does: ActiveModelSerializers.config.cache_store = config.action_controller.cache_store then, in the Railtie 1. ActiveSupport.on_load(:action_controller) fires - ActiveModelSerializers.config.cache_store #=> nil - ActionController::Base.cache_store #=> #<ActiveSupport::Cache::FileStore:0x007fe319256760...] 2. After set_configs fires - ActiveModelSerializers.config.cache_store #+> #<ActiveSupport::Cache::FileStore:0x007fe319256760 , 3. Tests pass, but notice that we're using the FileStore, not memory store When we change the config to the test app: ActionController::Base.cache_store = :memory_store config = Rails.configuration config.action_controller.cache_store = :memory_store then, in the Railtie: 1. ActiveSupport.on_load(:action_controller) fires - ActiveModelSerializers.config.cache_store #=> nil - ActionController::Base.cache_store #=> #ActiveSupport::Cache::MemoryStore entries=0, size=0, options={}>] 2. After set_configs fires - ActiveModelSerializers.config.cache_store #=> :memory_store 3. And we get a lot of failures: NoMethodError: undefined method `fetch' for :memory_store:Symbol So, we see that when we set the ActionController::Base.cache_store directly in our test app, we could set ActiveModelSerializers.config.cache_store in the :action_controller load hook, but that would never use the Rails config. To fix the Rails config, we change the config to the test app: config = Rails.configuration config.action_controller.cache_store = :memory_store and then AMS railtie does: ActiveModelSerializers.config.cache_store = ActiveSupport::Cache.lookup_store(config.action_controller.cache_store ActiveSupport.on_load(:action_controller) do ::ActiveModelSerializers.config.cache_store = cache_store end then 1. After set_configs fires - ActiveModelSerializers.config.cache_store #=> <#ActiveSupport::Cache::MemoryStore, object_id 70207113611740 2. ActiveSupport.on_load(:action_controller) fires - ActionController::Base.cache_store #=> <#ActiveSupport::Cache::MemoryStore, object_id 70207106279660 - ActiveModelSerializers.config.cache_store #=> <#ActiveSupport::Cache::MemoryStore, object_id 70207106279660 (notice the object_id changed) 3. And we get a failure: 1) Failure: ActiveModelSerializers::CacheTest#test_associations_cache_when_updated [active_model_serializers/test/cache_test.rb:141]: --- expected +++ actual @@ -1 +1 @@ -{:id=>"post", :title=>"New Post", :body=>"Body"} +{:id=>"post", :title=>"New Post", :body=>"Body", :comments=>[{:id=>2, :body=>"ZOMG A NEW COMMENT"}], :blog=>{:id=>999, :name=>"Custom blog"}, :author=>{:id=>"author", :name=>"Joao M. D. Moura"}} If we take out the on_load(:action_controller) hook, we get a ton of failures. So clearly, our code expects the controller cache to be the same as the serializer cache. So, we make sure we use an on_load(:action_controller) hook that runs after set_configs And look at the test and see it is filled with direct calls to ActionController::Base.cache_store assert_equal(new_comment_serializer.attributes, ActionController::Base.cache_store.fetch(new_comment.cache_key)) assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch(@post.cache_key)) But that's not a problem in this case, since they're the same object. For now, let's remove the :memory_store setting and use the default FileStore
It seems that fecthing from memory_store returns a reference to the object and not a copy. Since the Attributes adapter applies #merge! on the Hash that is returned from the memory_store, the value in the cache is also modified.
@@ -8,7 +8,7 @@ module ActiveModelSerializers | |||
# TODO: figure out why turning on the memory cache changes | |||
# the result of the CacheTest#test_associations_cache_when_updated | |||
# and if it is more correct or less correct. | |||
# config.action_controller.cache_store = :memory | |||
config.action_controller.cache_store = :memory_store |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💋
@groyoh Amazing! #1480 (comment) |
Bug found: The cache store needs to be the actually store, not e.g. :memory_store
Status quo in test app:
In Rails
and then AMS Railtie will look like:
then, in the AMS Railtie:
ActiveSupport.on_load(:action_controller)
firesActiveModelSerializers.config.cache_store #=> nil
ActionController::Base.cache_store #=> #<ActiveSupport::Cache::FileStore:0x007fe319256760...]
set_configs
firesActiveModelSerializers.config.cache_store #+> #<ActiveSupport::Cache::FileStore:0x007fe319256760 ,
When we change the config to the test app:
then, in the AMS Railtie:
ActiveSupport.on_load(:action_controller)
firesActiveModelSerializers.config.cache_store #=> nil
ActionController::Base.cache_store #=> #ActiveSupport::Cache::MemoryStore entries=0, size=0, options={}>]
set_configs
firesActiveModelSerializers.config.cache_store #=> :memory_store
NoMethodError: undefined method
fetch' for :memory_store:Symbol`So, we see that when we set the
ActionController::Base.cache_store
directly in our test app, we could set
ActiveModelSerializers.config.cache_store
in the:action_controller
loadhook, but that would never use the Rails config.
To fix the Rails config, we change the config to the test app:
and then AMS Railtie will look like:
then, in the AMS Railtie:
set_configs
firesActiveModelSerializers.config.cache_store #=> <#ActiveSupport::Cache::MemoryStore, object_id 70207113611740
ActiveSupport.on_load(:action_controller)
firesActionController::Base.cache_store #=> <#ActiveSupport::Cache::MemoryStore, object_id 70207106279660
ActiveModelSerializers.config.cache_store #=> <#ActiveSupport::Cache::MemoryStore, object_id 70207106279660
(notice the object_id changed)
If we take out the
on_load(:action_controller)
hook, we get a ton offailures. So clearly, our code expects the controller cache to be the
same as the serializer cache.
So, we make sure we use an
on_load(:action_controller)
hook that runsafter
set_configs
And look at the test and see it is filled with direct calls to
ActionController::Base.cache_store
But that's not a problem in this case, since they're the same object. That just explains all the failures before when they were different objects.
For now, let's remove the
:memory_store
setting and use the default FileStore and that one failure goes away.The cache log is