Distinguish options ivar from local; Extract latent Adapter::CachedSerializer

This commit is contained in:
Benjamin Fleischer 2015-09-16 22:12:13 -05:00
parent 3f0794bd39
commit 9d65f0adc5
10 changed files with 131 additions and 100 deletions

View File

@ -42,16 +42,16 @@ module ActiveModel
end end
def self.inherited(base) def self.inherited(base)
base._attributes = self._attributes.try(:dup) || [] base._attributes = _attributes.try(:dup) || []
base._attributes_keys = self._attributes_keys.try(:dup) || {} base._attributes_keys = _attributes_keys.try(:dup) || {}
base._cache_digest = digest_caller_file(caller.first) base._cache_digest = digest_caller_file(caller.first)
super super
end end
def self.attributes(*attrs) def self.attributes(*attrs)
attrs = attrs.first if attrs.first.class == Array attrs = attrs.first if attrs.first.class == Array
@_attributes.concat attrs _attributes.concat(attrs)
@_attributes.uniq! _attributes.uniq!
attrs.each do |attr| attrs.each do |attr|
define_method attr do define_method attr do
@ -62,8 +62,8 @@ module ActiveModel
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 self._attributes_keys[attr] = { key: key } if key != attr
@_attributes << key unless @_attributes.include?(key) self._attributes << key unless _attributes.include?(key)
ActiveModelSerializers.silence_warnings do ActiveModelSerializers.silence_warnings do
define_method key do define_method key do
@ -73,16 +73,16 @@ module ActiveModel
end end
def self.fragmented(serializer) def self.fragmented(serializer)
@_fragmented = serializer self._fragmented = serializer
end end
# Enables a serializer to be automatically cached # Enables a serializer to be automatically cached
def self.cache(options = {}) def self.cache(options = {})
@_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
@_cache_key = options.delete(:key) self._cache_key = options.delete(:key)
@_cache_only = options.delete(:only) self._cache_only = options.delete(:only)
@_cache_except = options.delete(:except) self._cache_except = options.delete(:except)
@_cache_options = (options.empty?) ? nil : options self._cache_options = (options.empty?) ? nil : options
end end
def self.serializer_for(resource, options = {}) def self.serializer_for(resource, options = {})
@ -91,7 +91,7 @@ module ActiveModel
elsif resource.respond_to?(:to_ary) elsif resource.respond_to?(:to_ary)
config.array_serializer config.array_serializer
else else
options.fetch(:serializer, get_serializer_for(resource.class)) options.fetch(:serializer) { get_serializer_for(resource.class) }
end end
end end
@ -104,17 +104,40 @@ module ActiveModel
name.demodulize.underscore.sub(/_serializer$/, '') if name name.demodulize.underscore.sub(/_serializer$/, '') if name
end end
def self.serializers_cache
@serializers_cache ||= ThreadSafe::Cache.new
end
def self.digest_caller_file(caller_line)
serializer_file_path = caller_line[CALLER_FILE]
serializer_file_contents = IO.read(serializer_file_path)
Digest::MD5.hexdigest(serializer_file_contents)
end
def self.get_serializer_for(klass)
serializers_cache.fetch_or_store(klass) do
serializer_class_name = "#{klass.name}Serializer"
serializer_class = serializer_class_name.safe_constantize
if serializer_class
serializer_class
elsif klass.superclass
get_serializer_for(klass.superclass)
end
end
end
attr_accessor :object, :root, :meta, :meta_key, :scope attr_accessor :object, :root, :meta, :meta_key, :scope
def initialize(object, options = {}) def initialize(object, options = {})
@object = object self.object = object
@options = options self.instance_options = options
@root = options[:root] self.root = instance_options[:root]
@meta = options[:meta] self.meta = instance_options[:meta]
@meta_key = options[:meta_key] self.meta_key = instance_options[:meta_key]
@scope = options[:scope] self.scope = instance_options[:scope]
scope_name = options[:scope_name] scope_name = instance_options[:scope_name]
if scope_name && !respond_to?(scope_name) if scope_name && !respond_to?(scope_name)
self.class.class_eval do self.class.class_eval do
define_method scope_name, lambda { scope } define_method scope_name, lambda { scope }
@ -123,7 +146,7 @@ module ActiveModel
end end
def json_key def json_key
@root || object.class.model_name.to_s.underscore root || object.class.model_name.to_s.underscore
end end
def attributes(options = {}) def attributes(options = {})
@ -143,29 +166,10 @@ module ActiveModel
end end
end end
def self.serializers_cache private # rubocop:disable Lint/UselessAccessModifier
@serializers_cache ||= ThreadSafe::Cache.new
end
def self.digest_caller_file(caller_line) ActiveModelSerializers.silence_warnings do
serializer_file_path = caller_line[CALLER_FILE] attr_accessor :instance_options
serializer_file_contents = IO.read(serializer_file_path)
Digest::MD5.hexdigest(serializer_file_contents)
end
attr_reader :options
def self.get_serializer_for(klass)
serializers_cache.fetch_or_store(klass) do
serializer_class_name = "#{klass.name}Serializer"
serializer_class = serializer_class_name.safe_constantize
if serializer_class
serializer_class
elsif klass.superclass
get_serializer_for(klass.superclass)
end
end
end end
end end
end end

View File

@ -10,6 +10,7 @@ module ActiveModel
autoload :JsonApi autoload :JsonApi
autoload :Null autoload :Null
autoload :FlattenJson autoload :FlattenJson
autoload :CachedSerializer
def self.create(resource, options = {}) def self.create(resource, options = {})
override = options.delete(:adapter) override = options.delete(:adapter)
@ -80,11 +81,11 @@ module ActiveModel
ActiveModel::Serializer::Adapter.register(subclass.to_s.demodulize, subclass) ActiveModel::Serializer::Adapter.register(subclass.to_s.demodulize, subclass)
end end
attr_reader :serializer attr_reader :serializer, :instance_options
def initialize(serializer, options = {}) def initialize(serializer, options = {})
@serializer = serializer @serializer = serializer
@options = options @instance_options = options
end end
def serializable_hash(options = nil) def serializable_hash(options = nil)
@ -101,41 +102,10 @@ module ActiveModel
raise NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.' raise NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.'
end end
private
def cache_check(serializer) def cache_check(serializer)
@cached_serializer = serializer CachedSerializer.new(serializer).cache_check(self) do
@klass = @cached_serializer.class
if is_cached?
@klass._cache.fetch(cache_key, @klass._cache_options) do
yield yield
end end
elsif is_fragment_cached?
FragmentCache.new(self, @cached_serializer, @options).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
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
def meta def meta

View File

@ -0,0 +1,45 @@
module ActiveModel
class Serializer
class Adapter
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
end
end

View File

@ -2,7 +2,7 @@ class ActiveModel::Serializer::Adapter::FragmentCache
attr_reader :serializer attr_reader :serializer
def initialize(adapter, serializer, options) def initialize(adapter, serializer, options)
@options = options @instance_options = options
@adapter = adapter @adapter = adapter
@serializer = serializer @serializer = serializer
end end
@ -16,19 +16,23 @@ class ActiveModel::Serializer::Adapter::FragmentCache
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)
cached_adapter = @adapter.class.new(cached_serializer, @options) cached_adapter = adapter.class.new(cached_serializer, instance_options)
non_cached_adapter = @adapter.class.new(non_cached_serializer, @options) non_cached_adapter = adapter.class.new(non_cached_serializer, instance_options)
# Get serializable hash from both # Get serializable hash from both
cached_hash = cached_adapter.serializable_hash cached_hash = cached_adapter.serializable_hash
non_cached_hash = non_cached_adapter.serializable_hash non_cached_hash = non_cached_adapter.serializable_hash
# Merge both results # Merge both results
@adapter.fragment_cache(cached_hash, non_cached_hash) adapter.fragment_cache(cached_hash, non_cached_hash)
end end
private private
ActiveModelSerializers.silence_warnings do
attr_reader :instance_options, :adapter
end
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) }

View File

@ -15,13 +15,13 @@ class ActiveModel::Serializer::Adapter::Json < ActiveModel::Serializer::Adapter
serializer.associations.each do |association| serializer.associations.each do |association|
serializer = association.serializer serializer = association.serializer
opts = association.options association_options = association.options
if serializer.respond_to?(:each) if serializer.respond_to?(:each)
array_serializer = serializer array_serializer = serializer
hash[association.key] = array_serializer.map do |item| hash[association.key] = array_serializer.map do |item|
cache_check(item) do cache_check(item) do
item.attributes(opts) item.attributes(association_options)
end end
end end
else else
@ -30,8 +30,8 @@ class ActiveModel::Serializer::Adapter::Json < ActiveModel::Serializer::Adapter
cache_check(serializer) do cache_check(serializer) do
serializer.attributes(options) serializer.attributes(options)
end end
elsif opts[:virtual_value] elsif association_options[:virtual_value]
opts[:virtual_value] association_options[:virtual_value]
end end
end end
end end

View File

@ -5,7 +5,7 @@ class ActiveModel::Serializer::Adapter::JsonApi < ActiveModel::Serializer::Adapt
def initialize(serializer, options = {}) def initialize(serializer, options = {})
super super
@included = ActiveModel::Serializer::Utils.include_args_to_hash(@options[:include]) @included = ActiveModel::Serializer::Utils.include_args_to_hash(instance_options[:include])
fields = options.delete(:fields) fields = options.delete(:fields)
if fields if fields
@fieldset = ActiveModel::Serializer::Fieldset.new(fields, serializer.json_key) @fieldset = ActiveModel::Serializer::Fieldset.new(fields, serializer.json_key)
@ -24,16 +24,20 @@ class ActiveModel::Serializer::Adapter::JsonApi < ActiveModel::Serializer::Adapt
end end
def fragment_cache(cached_hash, non_cached_hash) def fragment_cache(cached_hash, non_cached_hash)
root = false if @options.include?(:include) root = false if instance_options.include?(:include)
ActiveModel::Serializer::Adapter::JsonApi::FragmentCache.new.fragment_cache(root, cached_hash, non_cached_hash) ActiveModel::Serializer::Adapter::JsonApi::FragmentCache.new.fragment_cache(root, cached_hash, non_cached_hash)
end end
private private
ActiveModel.silence_warnings do
attr_reader :included, :fieldset
end
def serializable_hash_for_collection(serializer, options) def serializable_hash_for_collection(serializer, options)
hash = { data: [] } hash = { data: [] }
serializer.each do |s| serializer.each do |s|
result = self.class.new(s, @options.merge(fieldset: @fieldset)).serializable_hash(options) result = self.class.new(s, instance_options.merge(fieldset: fieldset)).serializable_hash(options)
hash[:data] << result[:data] hash[:data] << result[:data]
if result[:included] if result[:included]
@ -85,7 +89,7 @@ class ActiveModel::Serializer::Adapter::JsonApi < ActiveModel::Serializer::Adapt
end end
def resource_object_for(serializer, options = {}) def resource_object_for(serializer, options = {})
options[:fields] = @fieldset && @fieldset.fields_for(serializer) options[:fields] = fieldset && fieldset.fields_for(serializer)
cache_check(serializer) do cache_check(serializer) do
result = resource_identifier_for(serializer) result = resource_identifier_for(serializer)
@ -120,12 +124,10 @@ class ActiveModel::Serializer::Adapter::JsonApi < ActiveModel::Serializer::Adapt
end end
def included_for(serializer) def included_for(serializer)
included = @included.flat_map do |inc| included.flat_map { |inc|
association = serializer.associations.find { |assoc| assoc.key == inc.first } association = serializer.associations.find { |assoc| assoc.key == inc.first }
_included_for(association.serializer, inc.second) if association _included_for(association.serializer, inc.second) if association
end }.uniq
included.uniq
end end
def _included_for(serializer, includes) def _included_for(serializer, includes)
@ -134,7 +136,7 @@ class ActiveModel::Serializer::Adapter::JsonApi < ActiveModel::Serializer::Adapt
else else
return [] unless serializer && serializer.object return [] unless serializer && serializer.object
primary_data = primary_data_for(serializer, @options) primary_data = primary_data_for(serializer, instance_options)
relationships = relationships_for(serializer) relationships = relationships_for(serializer)
primary_data[:relationships] = relationships if relationships.any? primary_data[:relationships] = relationships if relationships.any?

View File

@ -26,7 +26,7 @@ module ActiveModel
end end
def json_key def json_key
key = root || @serializers.first.try(:json_key) || object.try(:name).try(:underscore) key = root || serializers.first.try(:json_key) || object.try(:name).try(:underscore)
key.try(:pluralize) key.try(:pluralize)
end end
@ -35,6 +35,12 @@ module ActiveModel
object.respond_to?(:total_pages) && object.respond_to?(:total_pages) &&
object.respond_to?(:size) object.respond_to?(:size)
end end
private # rubocop:disable Lint/UselessAccessModifier
ActiveModelSerializers.silence_warnings do
attr_reader :serializers
end
end end
end end
end end

View File

@ -88,7 +88,7 @@ module ActiveModel
Enumerator.new do |y| Enumerator.new do |y|
self.class._reflections.each do |reflection| self.class._reflections.each do |reflection|
y.yield reflection.build_association(self, options) y.yield reflection.build_association(self, instance_options)
end end
end end
end end

View File

@ -26,7 +26,7 @@ module ActiveModel
raw_fields.inject({}) { |h, (k, v)| h[k.to_sym] = v.map(&:to_sym); h } raw_fields.inject({}) { |h, (k, v)| h[k.to_sym] = v.map(&:to_sym); h }
elsif raw_fields.is_a?(Array) elsif raw_fields.is_a?(Array)
if root.nil? if root.nil?
raise ArgumentError, 'The root argument must be specified if the fields argument is an array.' raise ArgumentError, 'The root argument must be specified if the fields argument is an array.'.freeze
end end
hash = {} hash = {}
hash[root.to_sym] = raw_fields.map(&:to_sym) hash[root.to_sym] = raw_fields.map(&:to_sym)

View File

@ -60,7 +60,7 @@ class ProfileSerializer < ActiveModel::Serializer
attributes :name, :description attributes :name, :description
def arguments_passed_in? def arguments_passed_in?
options[:my_options] == :accessible instance_options[:my_options] == :accessible
end end
end end
@ -102,7 +102,7 @@ PostSerializer = Class.new(ActiveModel::Serializer) do
end end
def custom_options def custom_options
options instance_options
end end
end end
@ -123,7 +123,7 @@ CommentSerializer = Class.new(ActiveModel::Serializer) do
belongs_to :author belongs_to :author
def custom_options def custom_options
options instance_options
end end
end end