Skip to content
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

Ensuring read_multi works with fragment cache. #1814

Merged
merged 5 commits into from
Jun 23, 2016
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Breaking changes:
Features:

Fixes:
- [#1814] (https://github.com/rails-api/active_model_serializers/pull/1814) Ensuring read_multi works with fragment cache

Misc:

Expand Down
30 changes: 18 additions & 12 deletions lib/active_model/serializer/caching.rb
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def object_cache_keys(collection_serializer, adapter_instance, include_directive
def object_cache_key(serializer, adapter_instance)
return unless serializer.present? && serializer.object.present?

serializer.class.cache_enabled? ? serializer.cache_key(adapter_instance) : nil
(serializer.class.cache_enabled? || serializer.class.fragment_cache_enabled?) ? serializer.cache_key(adapter_instance) : nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You did push a lot of code that week, though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did I introduce it? It seemed always broken to me

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hahahhahaha, when you said Ooops I figured you had. Looks like I got it wrong.

end
end

Expand All @@ -211,7 +211,7 @@ def fetch_attributes(fields, cached_attributes, adapter_instance)
end
end
elsif serializer_class.fragment_cache_enabled?
fetch_attributes_fragment(adapter_instance)
fetch_attributes_fragment(adapter_instance, cached_attributes)
else
attributes(fields, true)
end
Expand All @@ -227,28 +227,34 @@ def fetch(adapter_instance, cache_options = serializer_class._cache_options)
end
end

def non_cached_attributes(adapter_instance, fields)
non_cached_fields = fields[:non_cached].dup
non_cached_hash = attributes(non_cached_fields, true)
include_directive = JSONAPI::IncludeDirective.new(non_cached_fields - non_cached_hash.keys)
non_cached_hash.merge! resource_relationships({}, { include_directive: include_directive }, adapter_instance)
non_cached_hash
end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like it was removed from fetch_attributes_fragment unchanged? was it a rubocop thing? The method needs refactor, I agree.. so sell me on why it's better with the non_cached_hash code moved to its own method (but not the cached_hash)?

Also, when adding methods, since this is in such flux, please prepend with comment # @api private and place them after where they are used

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rbocop thing. I would rather not change what it was, but it kept complaining about code complexity (the nested fetches).

Copy link
Member

@bf4 bf4 Jun 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, so just # rubocop:disable nameofwarnign at the top of the method and then # rubocop:enable nameofwarnign at the bottom as this is intentional

# 1. Determine cached fields from serializer class options
# 2. Get non_cached_fields and fetch cache_fields
# 3. Merge the two hashes using adapter_instance#fragment_cache
def fetch_attributes_fragment(adapter_instance)
def fetch_attributes_fragment(adapter_instance, cached_attributes = {})
serializer_class._cache_options ||= {}
serializer_class._cache_options[:key] = serializer_class._cache_key if serializer_class._cache_key
fields = serializer_class.fragmented_attributes

non_cached_fields = fields[:non_cached].dup
non_cached_hash = attributes(non_cached_fields, true)
include_directive = JSONAPI::IncludeDirective.new(non_cached_fields - non_cached_hash.keys)
non_cached_hash.merge! resource_relationships({}, { include_directive: include_directive }, adapter_instance)
non_cached_hash = non_cached_attributes(adapter_instance, fields)

cached_fields = fields[:cached].dup
key = cache_key(adapter_instance)
cached_hash =
serializer_class.cache_store.fetch(key, serializer_class._cache_options) do
hash = attributes(cached_fields, true)
include_directive = JSONAPI::IncludeDirective.new(cached_fields - hash.keys)
hash.merge! resource_relationships({}, { include_directive: include_directive }, adapter_instance)
cached_attributes.fetch(key) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this cached_attributes.fetch(key) do necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty much the line that makes it work for fragments. Is there something I am missing here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, brain 💨. was thinking in terms of the cache_store.fetch below, not that cached_attributes.fetch fires first in cache we read_multi already got it

serializer_class.cache_store.fetch(key, serializer_class._cache_options) do
hash = attributes(cached_fields, true)
include_directive = JSONAPI::IncludeDirective.new(cached_fields - hash.keys)
hash.merge! resource_relationships({}, { include_directive: include_directive }, adapter_instance)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether we could use MSET as well.

end

# Merge both results
adapter_instance.fragment_cache(cached_hash, non_cached_hash)
end
Expand Down
30 changes: 30 additions & 0 deletions test/cache_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,36 @@ def test_fetch_attributes_from_cache
end
end

def test_cache_read_multi_with_fragment_cache_enabled
post_serializer = Class.new(ActiveModel::Serializer) do
cache except: [:body]
end

serializers = ActiveModel::Serializer::CollectionSerializer.new([@post, @post], serializer: post_serializer)

Timecop.freeze(Time.current) do
# Warming up.
options = {}
adapter_options = {}
adapter_instance = ActiveModelSerializers::Adapter::Attributes.new(serializers, adapter_options)
serializers.serializable_hash(adapter_options, options, adapter_instance)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this adapter_instance .. sigh


# Should find something with read_multi now
options = {}
adapter_options = {}
adapter_instance = ActiveModelSerializers::Adapter::Attributes.new(serializers, adapter_options)
serializers.serializable_hash(adapter_options, options, adapter_instance)
cached_attributes = adapter_options.fetch(:cached_attributes)

include_directive = ActiveModelSerializers.default_include_directive
manual_cached_attributes = ActiveModel::Serializer.cache_read_multi(serializers, adapter_instance, include_directive)

refute_equal 0, cached_attributes.size
refute_equal 0, manual_cached_attributes.size
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we assert an actual number?

assert_equal manual_cached_attributes, cached_attributes
end
end

def test_serializer_file_path_on_nix
path = '/Users/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb'
caller_line = "#{path}:1:in `<top (required)>'"
Expand Down