mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-23 14:29:31 +00:00
161 lines
5.0 KiB
Ruby
161 lines
5.0 KiB
Ruby
require 'thread_safe'
|
|
require 'active_model/serializer/collection_serializer'
|
|
require 'active_model/serializer/array_serializer'
|
|
require 'active_model/serializer/include_tree'
|
|
require 'active_model/serializer/associations'
|
|
require 'active_model/serializer/attributes'
|
|
require 'active_model/serializer/caching'
|
|
require 'active_model/serializer/configuration'
|
|
require 'active_model/serializer/fieldset'
|
|
require 'active_model/serializer/lint'
|
|
require 'active_model/serializer/links'
|
|
require 'active_model/serializer/type'
|
|
|
|
# ActiveModel::Serializer is an abstract class that is
|
|
# reified when subclassed to decorate a resource.
|
|
module ActiveModel
|
|
class Serializer
|
|
include Configuration
|
|
include Associations
|
|
include Attributes
|
|
include Caching
|
|
include Links
|
|
include Type
|
|
require 'active_model/serializer/adapter'
|
|
|
|
with_options instance_writer: false, instance_reader: false do |serializer|
|
|
serializer.class_attribute :_meta # @api private : meta definition, @see Serializer#meta
|
|
end
|
|
|
|
# Register a meta attribute for the corresponding resource.
|
|
#
|
|
# @param [Hash] hash Optional hash
|
|
# @param [Block] block Optional block
|
|
def self.meta(hash = nil, &block)
|
|
self._meta =
|
|
if !block.nil?
|
|
block
|
|
else
|
|
hash
|
|
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 = {})
|
|
if resource.respond_to?(:serializer_class)
|
|
resource.serializer_class
|
|
elsif resource.respond_to?(:to_ary)
|
|
config.collection_serializer
|
|
else
|
|
options.fetch(:serializer) { get_serializer_for(resource.class) }
|
|
end
|
|
end
|
|
|
|
# @see ActiveModel::Serializer::Adapter.lookup
|
|
def self.adapter
|
|
ActiveModel::Serializer::Adapter.lookup(config.adapter)
|
|
end
|
|
|
|
# @api private
|
|
def self.serializer_lookup_chain_for(klass)
|
|
chain = []
|
|
|
|
resource_class_name = klass.name.demodulize
|
|
resource_namespace = klass.name.deconstantize
|
|
serializer_class_name = "#{resource_class_name}Serializer"
|
|
|
|
chain.push("#{name}::#{serializer_class_name}") if self != ActiveModel::Serializer
|
|
chain.push("#{resource_namespace}::#{serializer_class_name}")
|
|
|
|
chain
|
|
end
|
|
|
|
# Used to cache serializer name => serializer class
|
|
# when looked up by Serializer.get_serializer_for.
|
|
def self.serializers_cache
|
|
@serializers_cache ||= ThreadSafe::Cache.new
|
|
end
|
|
|
|
# @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)
|
|
return nil unless config.serializer_lookup_enabled
|
|
serializers_cache.fetch_or_store(klass) do
|
|
# NOTE(beauby): When we drop 1.9.3 support we can lazify the map for perfs.
|
|
serializer_class = serializer_lookup_chain_for(klass).map(&:safe_constantize).find { |x| x && x < ActiveModel::Serializer }
|
|
|
|
if serializer_class
|
|
serializer_class
|
|
elsif klass.superclass
|
|
get_serializer_for(klass.superclass)
|
|
end
|
|
end
|
|
end
|
|
|
|
def self._serializer_instance_method_defined?(name)
|
|
_serializer_instance_methods.include?(name)
|
|
end
|
|
|
|
def self._serializer_instance_methods
|
|
@_serializer_instance_methods ||= (public_instance_methods - Object.public_instance_methods).to_set
|
|
end
|
|
private_class_method :_serializer_instance_methods
|
|
|
|
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 = {})
|
|
self.object = object
|
|
self.instance_options = options
|
|
self.root = instance_options[:root]
|
|
self.scope = instance_options[:scope]
|
|
|
|
scope_name = instance_options[:scope_name]
|
|
if scope_name && !respond_to?(scope_name)
|
|
self.class.class_eval do
|
|
define_method scope_name, lambda { scope }
|
|
end
|
|
end
|
|
end
|
|
|
|
# Used by adapter as resource root.
|
|
def json_key
|
|
root || object.class.model_name.to_s.underscore
|
|
end
|
|
|
|
def read_attribute_for_serialization(attr)
|
|
if self.class._serializer_instance_method_defined?(attr)
|
|
send(attr)
|
|
elsif self.class._fragmented
|
|
self.class._fragmented.read_attribute_for_serialization(attr)
|
|
else
|
|
object.read_attribute_for_serialization(attr)
|
|
end
|
|
end
|
|
|
|
def meta
|
|
if self.class._meta.respond_to?(:call)
|
|
instance_eval(&self.class._meta)
|
|
else
|
|
self.class._meta
|
|
end
|
|
end
|
|
|
|
protected
|
|
|
|
attr_accessor :instance_options
|
|
end
|
|
end
|