mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-25 07:16:49 +00:00
Merge pull request #810 from joaomdmoura/fragment-cache
Adding Fragment Cache to AMS
This commit is contained in:
@@ -10,41 +10,54 @@ module ActiveModel
|
||||
|
||||
class << self
|
||||
attr_accessor :_attributes
|
||||
attr_accessor :_attributes_keys
|
||||
attr_accessor :_associations
|
||||
attr_accessor :_urls
|
||||
attr_accessor :_cache
|
||||
attr_accessor :_fragmented
|
||||
attr_accessor :_cache_key
|
||||
attr_accessor :_cache_only
|
||||
attr_accessor :_cache_except
|
||||
attr_accessor :_cache_options
|
||||
end
|
||||
|
||||
def self.inherited(base)
|
||||
base._attributes = []
|
||||
base._attributes_keys = {}
|
||||
base._associations = {}
|
||||
base._urls = []
|
||||
end
|
||||
|
||||
def self.attributes(*attrs)
|
||||
attrs = attrs.first if attrs.first.class == Array
|
||||
@_attributes.concat attrs
|
||||
|
||||
attrs.each do |attr|
|
||||
define_method attr do
|
||||
object && object.read_attribute_for_serialization(attr)
|
||||
end unless method_defined?(attr)
|
||||
end unless method_defined?(attr) || _fragmented.respond_to?(attr)
|
||||
end
|
||||
end
|
||||
|
||||
def self.attribute(attr, options = {})
|
||||
key = options.fetch(:key, attr)
|
||||
@_attributes_keys[attr] = {key: key} if key != attr
|
||||
@_attributes.concat [key]
|
||||
define_method key do
|
||||
object.read_attribute_for_serialization(attr)
|
||||
end unless method_defined?(key)
|
||||
end unless method_defined?(key) || _fragmented.respond_to?(attr)
|
||||
end
|
||||
|
||||
def self.fragmented(serializer)
|
||||
@_fragmented = serializer
|
||||
end
|
||||
|
||||
# Enables a serializer to be automatically cached
|
||||
def self.cache(options = {})
|
||||
@_cache = ActionController::Base.cache_store if Rails.configuration.action_controller.perform_caching
|
||||
@_cache_key = options.delete(:key)
|
||||
@_cache = ActionController::Base.cache_store if Rails.configuration.action_controller.perform_caching
|
||||
@_cache_key = options.delete(:key)
|
||||
@_cache_only = options.delete(:only)
|
||||
@_cache_except = options.delete(:except)
|
||||
@_cache_options = (options.empty?) ? nil : options
|
||||
end
|
||||
|
||||
@@ -141,12 +154,12 @@ module ActiveModel
|
||||
attr_accessor :object, :root, :meta, :meta_key, :scope
|
||||
|
||||
def initialize(object, options = {})
|
||||
@object = object
|
||||
@options = options
|
||||
@root = options[:root] || (self.class._root ? self.class.root_name : false)
|
||||
@meta = options[:meta]
|
||||
@meta_key = options[:meta_key]
|
||||
@scope = options[:scope]
|
||||
@object = object
|
||||
@options = options
|
||||
@root = options[:root] || (self.class._root ? self.class.root_name : false)
|
||||
@meta = options[:meta]
|
||||
@meta_key = options[:meta_key]
|
||||
@scope = options[:scope]
|
||||
|
||||
scope_name = options[:scope_name]
|
||||
if scope_name && !respond_to?(scope_name)
|
||||
@@ -183,22 +196,29 @@ module ActiveModel
|
||||
attributes += options[:required_fields] if options[:required_fields]
|
||||
|
||||
attributes.each_with_object({}) do |name, hash|
|
||||
hash[name] = send(name)
|
||||
unless self.class._fragmented
|
||||
hash[name] = send(name)
|
||||
else
|
||||
hash[name] = self.class._fragmented.public_send(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def each_association(&block)
|
||||
self.class._associations.dup.each do |name, association_options|
|
||||
next unless object
|
||||
|
||||
association_value = send(name)
|
||||
|
||||
serializer_class = ActiveModel::Serializer.serializer_for(association_value, association_options)
|
||||
|
||||
serializer = serializer_class.new(
|
||||
association_value,
|
||||
options.merge(serializer_from_options(association_options))
|
||||
) if serializer_class
|
||||
if serializer_class
|
||||
serializer = serializer_class.new(
|
||||
association_value,
|
||||
options.merge(serializer_from_options(association_options))
|
||||
)
|
||||
elsif !association_value.nil? && !association_value.instance_of?(Object)
|
||||
association_options[:association_options][:virtual_value] = association_value
|
||||
end
|
||||
|
||||
if block_given?
|
||||
block.call(name, serializer, association_options[:association_options])
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
require 'active_model/serializer/adapter/fragment_cache'
|
||||
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
class Adapter
|
||||
@@ -32,8 +34,38 @@ module ActiveModel
|
||||
"ActiveModel::Serializer::Adapter::#{adapter.to_s.classify}".safe_constantize
|
||||
end
|
||||
|
||||
def fragment_cache(*args)
|
||||
raise NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cache_check(serializer)
|
||||
@cached_serializer = serializer
|
||||
@klass = @cached_serializer.class
|
||||
if is_cached?
|
||||
@klass._cache.fetch(cache_key, @klass._cache_options) do
|
||||
yield
|
||||
end
|
||||
elsif is_fragment_cached?
|
||||
FragmentCache.new(self, @cached_serializer, @options, @root).fetch
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def is_cached?
|
||||
@klass._cache && !@klass._cache_only && !@klass._cache_except
|
||||
end
|
||||
|
||||
def is_fragment_cached?
|
||||
@klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except
|
||||
end
|
||||
|
||||
def cache_key
|
||||
(@klass._cache_key) ? "#{@klass._cache_key}/#{@cached_serializer.object.id}-#{@cached_serializer.object.updated_at}" : @cached_serializer.object.cache_key
|
||||
end
|
||||
|
||||
def meta
|
||||
serializer.meta if serializer.respond_to?(:meta)
|
||||
end
|
||||
@@ -50,20 +82,6 @@ module ActiveModel
|
||||
json[meta_key] = meta if meta && root
|
||||
json
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cached_object
|
||||
klass = serializer.class
|
||||
if klass._cache
|
||||
_cache_key = (klass._cache_key) ? "#{klass._cache_key}/#{serializer.object.id}-#{serializer.object.updated_at}" : serializer.object.cache_key
|
||||
klass._cache.fetch(_cache_key, klass._cache_options) do
|
||||
yield
|
||||
end
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
78
lib/active_model/serializer/adapter/fragment_cache.rb
Normal file
78
lib/active_model/serializer/adapter/fragment_cache.rb
Normal 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
|
||||
@@ -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
|
||||
15
lib/active_model/serializer/adapter/json/fragment_cache.rb
Normal file
15
lib/active_model/serializer/adapter/json/fragment_cache.rb
Normal 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
|
||||
@@ -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 }
|
||||
|
||||
@@ -77,24 +81,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
|
||||
|
||||
@@ -125,7 +132,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]
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user