-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move CachedSerializer/FragmentCache to AMS namespace
- Loading branch information
Showing
7 changed files
with
179 additions
and
184 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
require 'active_model_serializers/fragment_cache' | ||
module ActiveModelSerializers | ||
class CachedSerializer | ||
def initialize(serializer) | ||
@cached_serializer = serializer | ||
@klass = @cached_serializer.class | ||
end | ||
|
||
def cache_check(adapter_instance) | ||
if cached? | ||
@klass._cache.fetch(cache_key, @klass._cache_options) do | ||
yield | ||
end | ||
elsif fragment_cached? | ||
FragmentCache.new(adapter_instance, @cached_serializer, adapter_instance.instance_options).fetch | ||
else | ||
yield | ||
end | ||
end | ||
|
||
def cached? | ||
@klass._cache && !@klass._cache_only && !@klass._cache_except | ||
end | ||
|
||
def fragment_cached? | ||
@klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except | ||
end | ||
|
||
def cache_key | ||
parts = [] | ||
parts << object_cache_key | ||
parts << @klass._cache_digest unless @klass._cache_options && @klass._cache_options[:skip_digest] | ||
parts.join('/') | ||
end | ||
|
||
def object_cache_key | ||
object_time_safe = @cached_serializer.object.updated_at | ||
object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime) | ||
(@klass._cache_key) ? "#{@klass._cache_key}/#{@cached_serializer.object.id}-#{object_time_safe}" : @cached_serializer.object.cache_key | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
module ActiveModelSerializers | ||
class FragmentCache | ||
attr_reader :serializer | ||
|
||
def initialize(adapter, serializer, options) | ||
@instance_options = options | ||
@adapter = adapter | ||
@serializer = serializer | ||
end | ||
|
||
# TODO: Use Serializable::Resource | ||
# TODO: call +constantize+ less | ||
# 1. Create a CachedSerializer and NonCachedSerializer from the serializer class | ||
# 2. Serialize the above two with the given adapter | ||
# 3. Pass their serializations to the adapter +::fragment_cache+ | ||
def fetch | ||
klass = serializer.class | ||
# It will split the serializer into two, one that will be cached and one that will not | ||
serializers = fragment_serializer(serializer.object.class.name, klass) | ||
|
||
# Instantiate both serializers | ||
cached_serializer = serializers[:cached].constantize.new(serializer.object) | ||
non_cached_serializer = serializers[:non_cached].constantize.new(serializer.object) | ||
|
||
cached_adapter = adapter.class.new(cached_serializer, instance_options) | ||
non_cached_adapter = adapter.class.new(non_cached_serializer, instance_options) | ||
|
||
# Get serializable hash from both | ||
cached_hash = cached_adapter.serializable_hash | ||
non_cached_hash = non_cached_adapter.serializable_hash | ||
|
||
# Merge both results | ||
adapter.fragment_cache(cached_hash, non_cached_hash) | ||
end | ||
|
||
protected | ||
|
||
attr_reader :instance_options, :adapter | ||
|
||
private | ||
|
||
# Given a serializer class and a hash of its cached and non-cached serializers | ||
# 1. Determine cached attributes from serializer class options | ||
# 2. Add cached attributes to cached Serializer | ||
# 3. Add non-cached attributes to non-cached Serializer | ||
def cached_attributes(klass, serializers) | ||
attributes = serializer.class._attributes | ||
cached_attributes = (klass._cache_only) ? klass._cache_only : attributes.reject { |attr| klass._cache_except.include?(attr) } | ||
non_cached_attributes = attributes - cached_attributes | ||
|
||
cached_attributes.each do |attribute| | ||
options = serializer.class._attributes_keys[attribute] | ||
options ||= {} | ||
# Add cached attributes to cached Serializer | ||
serializers[:cached].constantize.attribute(attribute, options) | ||
end | ||
|
||
non_cached_attributes.each do |attribute| | ||
options = serializer.class._attributes_keys[attribute] | ||
options ||= {} | ||
# Add non-cached attributes to non-cached Serializer | ||
serializers[:non_cached].constantize.attribute(attribute, options) | ||
end | ||
end | ||
|
||
# Given a resource name and its serializer's class | ||
# 1. Dyanmically creates a CachedSerializer and NonCachedSerializer | ||
# for a given class 'name' | ||
# 2. Call | ||
# CachedSerializer.cache(serializer._cache_options) | ||
# CachedSerializer.fragmented(serializer) | ||
# NonCachedSerializer.cache(serializer._cache_options) | ||
# 3. Build a hash keyed to the +cached+ and +non_cached+ serializers | ||
# 4. Call +cached_attributes+ on the serializer class and the above hash | ||
# 5. Return the hash | ||
# | ||
# @example | ||
# When +name+ is <tt>User::Admin</tt> | ||
# creates the Serializer classes (if they don't exist). | ||
# User_AdminCachedSerializer | ||
# User_AdminNonCachedSerializer | ||
# | ||
def fragment_serializer(name, klass) | ||
cached = "#{to_valid_const_name(name)}CachedSerializer" | ||
non_cached = "#{to_valid_const_name(name)}NonCachedSerializer" | ||
|
||
Object.const_set cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(cached) | ||
Object.const_set non_cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(non_cached) | ||
|
||
klass._cache_options ||= {} | ||
klass._cache_options[:key] = klass._cache_key if klass._cache_key | ||
|
||
cached.constantize.cache(klass._cache_options) | ||
|
||
cached.constantize.fragmented(serializer) | ||
non_cached.constantize.fragmented(serializer) | ||
|
||
serializers = { cached: cached, non_cached: non_cached } | ||
cached_attributes(klass, serializers) | ||
serializers | ||
end | ||
|
||
def to_valid_const_name(name) | ||
name.gsub('::', '_') | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,33 @@ | ||
require 'test_helper' | ||
module ActiveModel | ||
class Serializer | ||
class FragmentCacheTest < ActiveSupport::TestCase | ||
def setup | ||
super | ||
@spam = Spam::UnrelatedLink.new(id: 'spam-id-1') | ||
@author = Author.new(name: 'Joao M. D. Moura') | ||
@role = Role.new(name: 'Great Author', description: nil) | ||
@role.author = [@author] | ||
@role_serializer = RoleSerializer.new(@role) | ||
@spam_serializer = Spam::UnrelatedLinkSerializer.new(@spam) | ||
@role_hash = FragmentCache.new(RoleSerializer.adapter.new(@role_serializer), @role_serializer, {}) | ||
@spam_hash = FragmentCache.new(Spam::UnrelatedLinkSerializer.adapter.new(@spam_serializer), @spam_serializer, {}) | ||
end | ||
module ActiveModelSerializers | ||
class FragmentCacheTest < ActiveSupport::TestCase | ||
def setup | ||
super | ||
@spam = Spam::UnrelatedLink.new(id: 'spam-id-1') | ||
@author = Author.new(name: 'Joao M. D. Moura') | ||
@role = Role.new(name: 'Great Author', description: nil) | ||
@role.author = [@author] | ||
@role_serializer = RoleSerializer.new(@role) | ||
@spam_serializer = Spam::UnrelatedLinkSerializer.new(@spam) | ||
@role_hash = FragmentCache.new(RoleSerializer.adapter.new(@role_serializer), @role_serializer, {}) | ||
@spam_hash = FragmentCache.new(Spam::UnrelatedLinkSerializer.adapter.new(@spam_serializer), @spam_serializer, {}) | ||
end | ||
|
||
def test_fragment_fetch_with_virtual_attributes | ||
expected_result = { | ||
id: @role.id, | ||
description: @role.description, | ||
slug: "#{@role.name}-#{@role.id}", | ||
name: @role.name | ||
} | ||
assert_equal(@role_hash.fetch, expected_result) | ||
end | ||
def test_fragment_fetch_with_virtual_attributes | ||
expected_result = { | ||
id: @role.id, | ||
description: @role.description, | ||
slug: "#{@role.name}-#{@role.id}", | ||
name: @role.name | ||
} | ||
assert_equal(@role_hash.fetch, expected_result) | ||
end | ||
|
||
def test_fragment_fetch_with_namespaced_object | ||
expected_result = { | ||
id: @spam.id | ||
} | ||
assert_equal(@spam_hash.fetch, expected_result) | ||
end | ||
def test_fragment_fetch_with_namespaced_object | ||
expected_result = { | ||
id: @spam.id | ||
} | ||
assert_equal(@spam_hash.fetch, expected_result) | ||
end | ||
end | ||
end |