mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-22 22:06:50 +00:00
Merge pull request #1260 from bf4/serializer_cache_digest
Serialization and Cache Documentation
This commit is contained in:
commit
86fa7a9bfc
@ -7,6 +7,8 @@ require 'active_model/serializer/configuration'
|
|||||||
require 'active_model/serializer/fieldset'
|
require 'active_model/serializer/fieldset'
|
||||||
require 'active_model/serializer/lint'
|
require 'active_model/serializer/lint'
|
||||||
|
|
||||||
|
# ActiveModel::Serializer is an abstract class that is
|
||||||
|
# reified when subclassed to decorate a resource.
|
||||||
module ActiveModel
|
module ActiveModel
|
||||||
class Serializer
|
class Serializer
|
||||||
include Configuration
|
include Configuration
|
||||||
@ -44,19 +46,28 @@ module ActiveModel
|
|||||||
|
|
||||||
with_options instance_writer: false, instance_reader: false do |serializer|
|
with_options instance_writer: false, instance_reader: false do |serializer|
|
||||||
class_attribute :_type, instance_reader: true
|
class_attribute :_type, instance_reader: true
|
||||||
class_attribute :_attributes
|
class_attribute :_attributes # @api private : names of attribute methods, @see Serializer#attribute
|
||||||
self._attributes ||= []
|
self._attributes ||= []
|
||||||
class_attribute :_attributes_keys
|
class_attribute :_attributes_keys # @api private : maps attribute value to explict key name, @see Serializer#attribute
|
||||||
self._attributes_keys ||= {}
|
self._attributes_keys ||= {}
|
||||||
serializer.class_attribute :_cache
|
serializer.class_attribute :_cache # @api private : the cache object
|
||||||
serializer.class_attribute :_fragmented
|
serializer.class_attribute :_fragmented # @api private : @see ::fragmented
|
||||||
serializer.class_attribute :_cache_key
|
serializer.class_attribute :_cache_key # @api private : when present, is first item in cache_key
|
||||||
serializer.class_attribute :_cache_only
|
serializer.class_attribute :_cache_only # @api private : when fragment caching, whitelists cached_attributes. Cannot combine with except
|
||||||
serializer.class_attribute :_cache_except
|
serializer.class_attribute :_cache_except # @api private : when fragment caching, blacklists cached_attributes. Cannot combine with only
|
||||||
serializer.class_attribute :_cache_options
|
serializer.class_attribute :_cache_options # @api private : used by CachedSerializer, passed to _cache.fetch
|
||||||
serializer.class_attribute :_cache_digest
|
# _cache_options include:
|
||||||
|
# expires_in
|
||||||
|
# compress
|
||||||
|
# force
|
||||||
|
# race_condition_ttl
|
||||||
|
# Passed to ::_cache as
|
||||||
|
# serializer._cache.fetch(cache_key, @klass._cache_options)
|
||||||
|
serializer.class_attribute :_cache_digest # @api private : Generated
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Serializers inherit _attributes and _attributes_keys.
|
||||||
|
# Generates a unique digest for each serializer at load.
|
||||||
def self.inherited(base)
|
def self.inherited(base)
|
||||||
caller_line = caller.first
|
caller_line = caller.first
|
||||||
base._attributes = _attributes.dup
|
base._attributes = _attributes.dup
|
||||||
@ -65,10 +76,16 @@ module ActiveModel
|
|||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @example
|
||||||
|
# class AdminAuthorSerializer < ActiveModel::Serializer
|
||||||
|
# type 'authors'
|
||||||
def self.type(type)
|
def self.type(type)
|
||||||
self._type = type
|
self._type = type
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @example
|
||||||
|
# class AdminAuthorSerializer < ActiveModel::Serializer
|
||||||
|
# attributes :id, :name, :recent_edits
|
||||||
def self.attributes(*attrs)
|
def self.attributes(*attrs)
|
||||||
attrs = attrs.first if attrs.first.class == Array
|
attrs = attrs.first if attrs.first.class == Array
|
||||||
|
|
||||||
@ -77,6 +94,14 @@ module ActiveModel
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @example
|
||||||
|
# class AdminAuthorSerializer < ActiveModel::Serializer
|
||||||
|
# attributes :id, :recent_edits
|
||||||
|
# attribute :name, key: :title
|
||||||
|
#
|
||||||
|
# def recent_edits
|
||||||
|
# object.edits.last(5)
|
||||||
|
# enr
|
||||||
def self.attribute(attr, options = {})
|
def self.attribute(attr, options = {})
|
||||||
key = options.fetch(:key, attr)
|
key = options.fetch(:key, attr)
|
||||||
_attributes_keys[attr] = { key: key } if key != attr
|
_attributes_keys[attr] = { key: key } if key != attr
|
||||||
@ -89,11 +114,35 @@ module ActiveModel
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @api private
|
||||||
|
# Used by FragmentCache on the CachedSerializer
|
||||||
|
# to call attribute methods on the fragmented cached serializer.
|
||||||
def self.fragmented(serializer)
|
def self.fragmented(serializer)
|
||||||
self._fragmented = serializer
|
self._fragmented = serializer
|
||||||
end
|
end
|
||||||
|
|
||||||
# Enables a serializer to be automatically cached
|
# Enables a serializer to be automatically cached
|
||||||
|
#
|
||||||
|
# Sets +::_cache+ object to <tt>ActionController::Base.cache_store</tt>
|
||||||
|
# when Rails.configuration.action_controller.perform_caching
|
||||||
|
#
|
||||||
|
# @params options [Hash] with valid keys:
|
||||||
|
# key : @see ::_cache_key
|
||||||
|
# only : @see ::_cache_only
|
||||||
|
# except : @see ::_cache_except
|
||||||
|
# skip_digest : does not include digest in cache_key
|
||||||
|
# all else : @see ::_cache_options
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# class PostSerializer < ActiveModel::Serializer
|
||||||
|
# cache key: 'post', expires_in: 3.hours
|
||||||
|
# attributes :title, :body
|
||||||
|
#
|
||||||
|
# has_many :comments
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# @todo require less code comments. See
|
||||||
|
# https://github.com/rails-api/active_model_serializers/pull/1249#issuecomment-146567837
|
||||||
def self.cache(options = {})
|
def self.cache(options = {})
|
||||||
self._cache = ActionController::Base.cache_store if Rails.configuration.action_controller.perform_caching
|
self._cache = ActionController::Base.cache_store if Rails.configuration.action_controller.perform_caching
|
||||||
self._cache_key = options.delete(:key)
|
self._cache_key = options.delete(:key)
|
||||||
@ -102,6 +151,13 @@ module ActiveModel
|
|||||||
self._cache_options = (options.empty?) ? nil : options
|
self._cache_options = (options.empty?) ? nil : options
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @param resource [ActiveRecord::Base, ActiveModelSerializers::Model]
|
||||||
|
# @return [ActiveModel::Serializer]
|
||||||
|
# Preferentially returns
|
||||||
|
# 1. resource.serializer
|
||||||
|
# 2. ArraySerializer when resource is a collection
|
||||||
|
# 3. options[:serializer]
|
||||||
|
# 4. lookup serializer when resource is a Class
|
||||||
def self.serializer_for(resource, options = {})
|
def self.serializer_for(resource, options = {})
|
||||||
if resource.respond_to?(:serializer_class)
|
if resource.respond_to?(:serializer_class)
|
||||||
resource.serializer_class
|
resource.serializer_class
|
||||||
@ -117,6 +173,8 @@ module ActiveModel
|
|||||||
ActiveModel::Serializer::Adapter.lookup(config.adapter)
|
ActiveModel::Serializer::Adapter.lookup(config.adapter)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Used to cache serializer name => serializer class
|
||||||
|
# when looked up by Serializer.get_serializer_for.
|
||||||
def self.serializers_cache
|
def self.serializers_cache
|
||||||
@serializers_cache ||= ThreadSafe::Cache.new
|
@serializers_cache ||= ThreadSafe::Cache.new
|
||||||
end
|
end
|
||||||
@ -136,6 +194,11 @@ module ActiveModel
|
|||||||
end
|
end
|
||||||
|
|
||||||
# @api private
|
# @api private
|
||||||
|
# Find a serializer from a class and caches the lookup.
|
||||||
|
# Preferentially retuns:
|
||||||
|
# 1. class name appended with "Serializer"
|
||||||
|
# 2. try again with superclass, if present
|
||||||
|
# 3. nil
|
||||||
def self.get_serializer_for(klass)
|
def self.get_serializer_for(klass)
|
||||||
serializers_cache.fetch_or_store(klass) do
|
serializers_cache.fetch_or_store(klass) do
|
||||||
# NOTE(beauby): When we drop 1.9.3 support we can lazify the map for perfs.
|
# NOTE(beauby): When we drop 1.9.3 support we can lazify the map for perfs.
|
||||||
@ -151,6 +214,9 @@ module ActiveModel
|
|||||||
|
|
||||||
attr_accessor :object, :root, :scope
|
attr_accessor :object, :root, :scope
|
||||||
|
|
||||||
|
# `scope_name` is set as :current_user by default in the controller.
|
||||||
|
# If the instance does not have a method named `scope_name`, it
|
||||||
|
# defines the method so that it calls the +scope+.
|
||||||
def initialize(object, options = {})
|
def initialize(object, options = {})
|
||||||
self.object = object
|
self.object = object
|
||||||
self.instance_options = options
|
self.instance_options = options
|
||||||
@ -165,10 +231,13 @@ module ActiveModel
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Used by adapter as resource root.
|
||||||
def json_key
|
def json_key
|
||||||
root || object.class.model_name.to_s.underscore
|
root || object.class.model_name.to_s.underscore
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Return the +attributes+ of +object+ as presented
|
||||||
|
# by the serializer.
|
||||||
def attributes
|
def attributes
|
||||||
attributes = self.class._attributes.dup
|
attributes = self.class._attributes.dup
|
||||||
|
|
||||||
|
|||||||
@ -10,12 +10,17 @@ module ActiveModel
|
|||||||
@serializer = serializer
|
@serializer = serializer
|
||||||
end
|
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
|
def fetch
|
||||||
klass = serializer.class
|
klass = serializer.class
|
||||||
# It will split the serializer into two, one that will be cached and other wont
|
# 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)
|
serializers = fragment_serializer(serializer.object.class.name, klass)
|
||||||
|
|
||||||
# Instanciate both serializers
|
# Instantiate both serializers
|
||||||
cached_serializer = serializers[:cached].constantize.new(serializer.object)
|
cached_serializer = serializers[:cached].constantize.new(serializer.object)
|
||||||
non_cached_serializer = serializers[:non_cached].constantize.new(serializer.object)
|
non_cached_serializer = serializers[:non_cached].constantize.new(serializer.object)
|
||||||
|
|
||||||
@ -36,6 +41,10 @@ module ActiveModel
|
|||||||
|
|
||||||
private
|
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)
|
def cached_attributes(klass, serializers)
|
||||||
attributes = serializer.class._attributes
|
attributes = serializer.class._attributes
|
||||||
cached_attributes = (klass._cache_only) ? klass._cache_only : attributes.reject { |attr| klass._cache_except.include?(attr) }
|
cached_attributes = (klass._cache_only) ? klass._cache_only : attributes.reject { |attr| klass._cache_except.include?(attr) }
|
||||||
@ -56,6 +65,23 @@ module ActiveModel
|
|||||||
end
|
end
|
||||||
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)
|
||||||
|
# NontCachedSerializer.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)
|
def fragment_serializer(name, klass)
|
||||||
cached = "#{to_valid_const_name(name)}CachedSerializer"
|
cached = "#{to_valid_const_name(name)}CachedSerializer"
|
||||||
non_cached = "#{to_valid_const_name(name)}NonCachedSerializer"
|
non_cached = "#{to_valid_const_name(name)}NonCachedSerializer"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user