Adding Fragment Cache to AMS

It's an upgrade based on the new Cache implementation #693.
It allows to use the Rails conventions to cache
specific attributes or associations.
It's based on the Cache Composition implementation.
This commit is contained in:
João Moura
2015-02-03 21:57:02 -02:00
parent 48ed7cf9ba
commit 792fb8a905
19 changed files with 554 additions and 101 deletions

View File

@@ -0,0 +1,78 @@
module ActiveModel
class Serializer
class Adapter
class FragmentCache
attr_reader :serializer
def initialize(adapter, serializer, options, root)
@root = root
@options = options
@adapter = adapter
@serializer = serializer
end
def fetch
klass = serializer.class
# It will split the serializer into two, one that will be cached and other wont
serializers = fragment_serializer(serializer.object.class.name, klass)
# Instanciate 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, @options)
non_cached_adapter = @adapter.class.new(non_cached_serializer, @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
private
def cached_attributes(klass, serializers)
cached_attributes = (klass._cache_only) ? klass._cache_only : serializer.attributes.keys.delete_if {|attr| klass._cache_except.include?(attr) }
non_cached_attributes = serializer.attributes.keys.delete_if {|attr| cached_attributes.include?(attr) }
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
def fragment_serializer(name, klass)
cached = "#{name.capitalize}CachedSerializer"
non_cached = "#{name.capitalize}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
end
end
end
end

View File

@@ -1,3 +1,5 @@
require 'active_model/serializer/adapter/json/fragment_cache'
module ActiveModel
class Serializer
class Adapter
@@ -6,31 +8,45 @@ module ActiveModel
if serializer.respond_to?(:each)
@result = serializer.map{|s| self.class.new(s).serializable_hash }
else
@result = cached_object do
@hash = serializer.attributes(options)
serializer.each_association do |name, association, opts|
if association.respond_to?(:each)
array_serializer = association
@hash[name] = array_serializer.map { |item| item.attributes(opts) }
else
if association
@hash[name] = association.attributes(options)
else
@hash[name] = nil
@hash = {}
@core = cache_check(serializer) do
serializer.attributes(options)
end
serializer.each_association do |name, association, opts|
if association.respond_to?(:each)
array_serializer = association
@hash[name] = array_serializer.map do |item|
cache_check(item) do
item.attributes(opts)
end
end
else
if association
@hash[name] = cache_check(association) do
association.attributes(options)
end
elsif opts[:virtual_value]
@hash[name] = opts[:virtual_value]
else
@hash[name] = nil
end
end
@hash
end
@result = @core.merge @hash
end
if root = options.fetch(:root, serializer.json_key)
@result = { root => @result }
end
@result
end
end
def fragment_cache(cached_hash, non_cached_hash)
Json::FragmentCache.new().fragment_cache(cached_hash, non_cached_hash)
end
end
end
end
end

View File

@@ -0,0 +1,15 @@
module ActiveModel
class Serializer
class Adapter
class Json < Adapter
class FragmentCache
def fragment_cache(cached_hash, non_cached_hash)
non_cached_hash.merge cached_hash
end
end
end
end
end
end

View File

@@ -1,3 +1,5 @@
require 'active_model/serializer/adapter/json_api/fragment_cache'
module ActiveModel
class Serializer
class Adapter
@@ -26,15 +28,17 @@ module ActiveModel
end
end
else
@hash = cached_object do
@hash[:data] = attributes_for_serializer(serializer, @options)
add_resource_links(@hash[:data], serializer)
@hash
end
@hash[:data] = attributes_for_serializer(serializer, @options)
add_resource_links(@hash[:data], serializer)
end
@hash
end
def fragment_cache(cached_hash, non_cached_hash)
root = false if @options.include?(:include)
JsonApi::FragmentCache.new().fragment_cache(root, cached_hash, non_cached_hash)
end
private
def add_links(resource, name, serializers)
@@ -43,7 +47,7 @@ module ActiveModel
resource[:links][name][:linkage] += serializers.map { |serializer| { type: serializer.type, id: serializer.id.to_s } }
end
def add_link(resource, name, serializer)
def add_link(resource, name, serializer, val=nil)
resource[:links] ||= {}
resource[:links][name] = { linkage: nil }
@@ -76,24 +80,27 @@ module ActiveModel
end
end
def attributes_for_serializer(serializer, options)
if serializer.respond_to?(:each)
result = []
serializer.each do |object|
options[:fields] = @fieldset && @fieldset.fields_for(serializer)
options[:required_fields] = [:id, :type]
attributes = object.attributes(options)
attributes[:id] = attributes[:id].to_s
result << attributes
result << cache_check(object) do
options[:required_fields] = [:id, :type]
attributes = object.attributes(options)
attributes[:id] = attributes[:id].to_s
result << attributes
end
end
else
options[:fields] = @fieldset && @fieldset.fields_for(serializer)
options[:required_fields] = [:id, :type]
result = serializer.attributes(options)
result[:id] = result[:id].to_s
result = cache_check(serializer) do
result = serializer.attributes(options)
result[:id] = result[:id].to_s
result
end
end
result
end
@@ -124,7 +131,11 @@ module ActiveModel
if association.respond_to?(:each)
add_links(attrs, name, association)
else
add_link(attrs, name, association)
if opts[:virtual_value]
add_link(attrs, name, nil, opts[:virtual_value])
else
add_link(attrs, name, association)
end
end
if options[:add_included]

View File

@@ -0,0 +1,22 @@
module ActiveModel
class Serializer
class Adapter
class JsonApi < Adapter
class FragmentCache
def fragment_cache(root, cached_hash, non_cached_hash)
hash = {}
core_cached = cached_hash.first
core_non_cached = non_cached_hash.first
no_root_cache = cached_hash.delete_if {|key, value| key == core_cached[0] }
no_root_non_cache = non_cached_hash.delete_if {|key, value| key == core_non_cached[0] }
cached_resource = (core_cached[1]) ? core_cached[1].merge(core_non_cached[1]) : core_non_cached[1]
hash = (root) ? { root => cached_resource } : cached_resource
hash.merge no_root_non_cache.merge no_root_cache
end
end
end
end
end
end