mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-23 06:16:50 +00:00
Merge pull request #985 from bolshakov/feature/each_association
Associations implementation refactoring
This commit is contained in:
commit
c703d0f35c
@ -3,16 +3,18 @@ require 'thread_safe'
|
|||||||
module ActiveModel
|
module ActiveModel
|
||||||
class Serializer
|
class Serializer
|
||||||
extend ActiveSupport::Autoload
|
extend ActiveSupport::Autoload
|
||||||
|
|
||||||
autoload :Configuration
|
autoload :Configuration
|
||||||
autoload :ArraySerializer
|
autoload :ArraySerializer
|
||||||
autoload :Adapter
|
autoload :Adapter
|
||||||
autoload :Lint
|
autoload :Lint
|
||||||
|
autoload :Associations
|
||||||
include Configuration
|
include Configuration
|
||||||
|
include Associations
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
attr_accessor :_attributes
|
attr_accessor :_attributes
|
||||||
attr_accessor :_attributes_keys
|
attr_accessor :_attributes_keys
|
||||||
attr_accessor :_associations
|
|
||||||
attr_accessor :_urls
|
attr_accessor :_urls
|
||||||
attr_accessor :_cache
|
attr_accessor :_cache
|
||||||
attr_accessor :_fragmented
|
attr_accessor :_fragmented
|
||||||
@ -24,12 +26,12 @@ module ActiveModel
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.inherited(base)
|
def self.inherited(base)
|
||||||
base._attributes = self._attributes.try(:dup) || []
|
base._attributes = self._attributes.try(:dup) || []
|
||||||
base._attributes_keys = self._attributes_keys.try(:dup) || {}
|
base._attributes_keys = self._attributes_keys.try(:dup) || {}
|
||||||
base._associations = self._associations.try(:dup) || {}
|
|
||||||
base._urls = []
|
base._urls = []
|
||||||
serializer_file = File.open(caller.first[/^[^:]+/])
|
serializer_file = File.open(caller.first[/^[^:]+/])
|
||||||
base._cache_digest = Digest::MD5.hexdigest(serializer_file.read)
|
base._cache_digest = Digest::MD5.hexdigest(serializer_file.read)
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.attributes(*attrs)
|
def self.attributes(*attrs)
|
||||||
@ -46,7 +48,7 @@ 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
|
@_attributes_keys[attr] = { key: key } if key != attr
|
||||||
@_attributes << key unless @_attributes.include?(key)
|
@_attributes << key unless @_attributes.include?(key)
|
||||||
define_method key do
|
define_method key do
|
||||||
object.read_attribute_for_serialization(attr)
|
object.read_attribute_for_serialization(attr)
|
||||||
@ -59,58 +61,13 @@ module ActiveModel
|
|||||||
|
|
||||||
# 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
|
@_cache = ActionController::Base.cache_store if Rails.configuration.action_controller.perform_caching
|
||||||
@_cache_key = options.delete(:key)
|
@_cache_key = options.delete(:key)
|
||||||
@_cache_only = options.delete(:only)
|
@_cache_only = options.delete(:only)
|
||||||
@_cache_except = options.delete(:except)
|
@_cache_except = options.delete(:except)
|
||||||
@_cache_options = (options.empty?) ? nil : options
|
@_cache_options = (options.empty?) ? nil : options
|
||||||
end
|
end
|
||||||
|
|
||||||
# Defines an association in the object should be rendered.
|
|
||||||
#
|
|
||||||
# The serializer object should implement the association name
|
|
||||||
# as a method which should return an array when invoked. If a method
|
|
||||||
# with the association name does not exist, the association name is
|
|
||||||
# dispatched to the serialized object.
|
|
||||||
def self.has_many(*attrs)
|
|
||||||
associate(:has_many, attrs)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Defines an association in the object that should be rendered.
|
|
||||||
#
|
|
||||||
# The serializer object should implement the association name
|
|
||||||
# as a method which should return an object when invoked. If a method
|
|
||||||
# with the association name does not exist, the association name is
|
|
||||||
# dispatched to the serialized object.
|
|
||||||
def self.belongs_to(*attrs)
|
|
||||||
associate(:belongs_to, attrs)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Defines an association in the object should be rendered.
|
|
||||||
#
|
|
||||||
# The serializer object should implement the association name
|
|
||||||
# as a method which should return an object when invoked. If a method
|
|
||||||
# with the association name does not exist, the association name is
|
|
||||||
# dispatched to the serialized object.
|
|
||||||
def self.has_one(*attrs)
|
|
||||||
associate(:has_one, attrs)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.associate(type, attrs) #:nodoc:
|
|
||||||
options = attrs.extract_options!
|
|
||||||
self._associations = _associations.dup
|
|
||||||
|
|
||||||
attrs.each do |attr|
|
|
||||||
unless method_defined?(attr)
|
|
||||||
define_method attr do
|
|
||||||
object.send attr
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self._associations[attr] = {type: type, association_options: options}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.url(attr)
|
def self.url(attr)
|
||||||
@_urls.push attr
|
@_urls.push attr
|
||||||
end
|
end
|
||||||
@ -125,19 +82,17 @@ module ActiveModel
|
|||||||
elsif resource.respond_to?(:to_ary)
|
elsif resource.respond_to?(:to_ary)
|
||||||
config.array_serializer
|
config.array_serializer
|
||||||
else
|
else
|
||||||
options
|
options.fetch(:serializer, get_serializer_for(resource.class))
|
||||||
.fetch(:association_options, {})
|
|
||||||
.fetch(:serializer, get_serializer_for(resource.class))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.adapter
|
def self.adapter
|
||||||
adapter_class = case config.adapter
|
adapter_class = case config.adapter
|
||||||
when Symbol
|
when Symbol
|
||||||
ActiveModel::Serializer::Adapter.adapter_class(config.adapter)
|
ActiveModel::Serializer::Adapter.adapter_class(config.adapter)
|
||||||
when Class
|
when Class
|
||||||
config.adapter
|
config.adapter
|
||||||
end
|
end
|
||||||
unless adapter_class
|
unless adapter_class
|
||||||
valid_adapters = Adapter.constants.map { |klass| ":#{klass.to_s.downcase}" }
|
valid_adapters = Adapter.constants.map { |klass| ":#{klass.to_s.downcase}" }
|
||||||
raise ArgumentError, "Unknown adapter: #{config.adapter}. Valid adapters are: #{valid_adapters}"
|
raise ArgumentError, "Unknown adapter: #{config.adapter}. Valid adapters are: #{valid_adapters}"
|
||||||
@ -153,12 +108,12 @@ module ActiveModel
|
|||||||
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
|
@object = object
|
||||||
@options = options
|
@options = options
|
||||||
@root = options[:root]
|
@root = options[:root]
|
||||||
@meta = options[:meta]
|
@meta = options[:meta]
|
||||||
@meta_key = options[:meta_key]
|
@meta_key = options[:meta_key]
|
||||||
@scope = options[:scope]
|
@scope = options[:scope]
|
||||||
|
|
||||||
scope_name = options[:scope_name]
|
scope_name = options[:scope_name]
|
||||||
if scope_name && !respond_to?(scope_name)
|
if scope_name && !respond_to?(scope_name)
|
||||||
@ -199,48 +154,10 @@ module ActiveModel
|
|||||||
end
|
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)
|
|
||||||
|
|
||||||
if serializer_class
|
|
||||||
begin
|
|
||||||
serializer = serializer_class.new(
|
|
||||||
association_value,
|
|
||||||
options.except(:serializer).merge(serializer_from_options(association_options))
|
|
||||||
)
|
|
||||||
rescue ActiveModel::Serializer::ArraySerializer::NoSerializerError
|
|
||||||
virtual_value = association_value
|
|
||||||
virtual_value = virtual_value.as_json if virtual_value.respond_to?(:as_json)
|
|
||||||
association_options[:association_options][:virtual_value] = virtual_value
|
|
||||||
end
|
|
||||||
elsif !association_value.nil? && !association_value.instance_of?(Object)
|
|
||||||
association_options[:association_options][:virtual_value] = association_value
|
|
||||||
end
|
|
||||||
|
|
||||||
association_key = association_options[:association_options][:key] || name
|
|
||||||
if block_given?
|
|
||||||
block.call(association_key, serializer, association_options[:association_options])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def serializer_from_options(options)
|
|
||||||
opts = {}
|
|
||||||
serializer = options.fetch(:association_options, {}).fetch(:serializer, nil)
|
|
||||||
opts[:serializer] = serializer if serializer
|
|
||||||
opts
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.serializers_cache
|
def self.serializers_cache
|
||||||
@serializers_cache ||= ThreadSafe::Cache.new
|
@serializers_cache ||= ThreadSafe::Cache.new
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
attr_reader :options
|
attr_reader :options
|
||||||
|
|
||||||
def self.get_serializer_for(klass)
|
def self.get_serializer_for(klass)
|
||||||
@ -255,6 +172,5 @@ module ActiveModel
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -7,7 +7,7 @@ module ActiveModel
|
|||||||
def serializable_hash(options = nil)
|
def serializable_hash(options = nil)
|
||||||
options ||= {}
|
options ||= {}
|
||||||
if serializer.respond_to?(:each)
|
if serializer.respond_to?(:each)
|
||||||
@result = serializer.map{|s| FlattenJson.new(s).serializable_hash(options) }
|
@result = serializer.map { |s| FlattenJson.new(s).serializable_hash(options) }
|
||||||
else
|
else
|
||||||
@hash = {}
|
@hash = {}
|
||||||
|
|
||||||
@ -15,24 +15,26 @@ module ActiveModel
|
|||||||
serializer.attributes(options)
|
serializer.attributes(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
serializer.each_association do |key, association, opts|
|
serializer.associations.each do |association|
|
||||||
if association.respond_to?(:each)
|
serializer = association.serializer
|
||||||
array_serializer = association
|
opts = association.options
|
||||||
@hash[key] = array_serializer.map do |item|
|
|
||||||
|
if serializer.respond_to?(:each)
|
||||||
|
array_serializer = serializer
|
||||||
|
@hash[association.key] = array_serializer.map do |item|
|
||||||
cache_check(item) do
|
cache_check(item) do
|
||||||
item.attributes(opts)
|
item.attributes(opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if association && association.object
|
@hash[association.key] =
|
||||||
@hash[key] = cache_check(association) do
|
if serializer && serializer.object
|
||||||
association.attributes(options)
|
cache_check(serializer) do
|
||||||
|
serializer.attributes(options)
|
||||||
|
end
|
||||||
|
elsif opts[:virtual_value]
|
||||||
|
opts[:virtual_value]
|
||||||
end
|
end
|
||||||
elsif opts[:virtual_value]
|
|
||||||
@hash[key] = opts[:virtual_value]
|
|
||||||
else
|
|
||||||
@hash[key] = nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@result = @core.merge @hash
|
@result = @core.merge @hash
|
||||||
|
|||||||
@ -75,8 +75,10 @@ module ActiveModel
|
|||||||
end
|
end
|
||||||
|
|
||||||
serializers.each do |serializer|
|
serializers.each do |serializer|
|
||||||
serializer.each_association do |key, association, opts|
|
serializer.associations.each do |association|
|
||||||
add_included(key, association, resource_path) if association
|
serializer = association.serializer
|
||||||
|
|
||||||
|
add_included(association.key, serializer, resource_path) if serializer
|
||||||
end if include_nested_assoc? resource_path
|
end if include_nested_assoc? resource_path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -131,22 +133,26 @@ module ActiveModel
|
|||||||
def add_resource_relationships(attrs, serializer, options = {})
|
def add_resource_relationships(attrs, serializer, options = {})
|
||||||
options[:add_included] = options.fetch(:add_included, true)
|
options[:add_included] = options.fetch(:add_included, true)
|
||||||
|
|
||||||
serializer.each_association do |key, association, opts|
|
serializer.associations.each do |association|
|
||||||
|
key = association.key
|
||||||
|
serializer = association.serializer
|
||||||
|
opts = association.options
|
||||||
|
|
||||||
attrs[:relationships] ||= {}
|
attrs[:relationships] ||= {}
|
||||||
|
|
||||||
if association.respond_to?(:each)
|
if serializer.respond_to?(:each)
|
||||||
add_relationships(attrs, key, association)
|
add_relationships(attrs, key, serializer)
|
||||||
else
|
else
|
||||||
if opts[:virtual_value]
|
if opts[:virtual_value]
|
||||||
add_relationship(attrs, key, nil, opts[:virtual_value])
|
add_relationship(attrs, key, nil, opts[:virtual_value])
|
||||||
else
|
else
|
||||||
add_relationship(attrs, key, association)
|
add_relationship(attrs, key, serializer)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if options[:add_included]
|
if options[:add_included]
|
||||||
Array(association).each do |association|
|
Array(serializer).each do |serializer|
|
||||||
add_included(key, association)
|
add_included(key, serializer)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
21
lib/active_model/serializer/association.rb
Normal file
21
lib/active_model/serializer/association.rb
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
module ActiveModel
|
||||||
|
class Serializer
|
||||||
|
# This class hold all information about serializer's association.
|
||||||
|
#
|
||||||
|
# @param [Symbol] name
|
||||||
|
# @param [ActiveModel::Serializer] serializer
|
||||||
|
# @param [Hash{Symbol => Object}] options
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# Association.new(:comments, CommentSummarySerializer, embed: :ids)
|
||||||
|
#
|
||||||
|
Association = Struct.new(:name, :serializer, :options) do
|
||||||
|
|
||||||
|
# @return [Symbol]
|
||||||
|
#
|
||||||
|
def key
|
||||||
|
options.fetch(:key, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
107
lib/active_model/serializer/associations.rb
Normal file
107
lib/active_model/serializer/associations.rb
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
module ActiveModel
|
||||||
|
class Serializer
|
||||||
|
# Defines an association in the object should be rendered.
|
||||||
|
#
|
||||||
|
# The serializer object should implement the association name
|
||||||
|
# as a method which should return an array when invoked. If a method
|
||||||
|
# with the association name does not exist, the association name is
|
||||||
|
# dispatched to the serialized object.
|
||||||
|
#
|
||||||
|
module Associations
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do |base|
|
||||||
|
class << base
|
||||||
|
attr_accessor :_reflections
|
||||||
|
end
|
||||||
|
|
||||||
|
autoload :Association
|
||||||
|
autoload :Reflection
|
||||||
|
autoload :SingularReflection
|
||||||
|
autoload :CollectionReflection
|
||||||
|
autoload :BelongsToReflection
|
||||||
|
autoload :HasOneReflection
|
||||||
|
autoload :HasManyReflection
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def inherited(base)
|
||||||
|
base._reflections = self._reflections.try(:dup) || []
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [Array(Array<Symbol>, Hash{Symbol => Object})] attrs
|
||||||
|
# @return [void]
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# has_many :comments, serializer: CommentSummarySerializer
|
||||||
|
# has_many :commits, authors
|
||||||
|
#
|
||||||
|
def has_many(*attrs)
|
||||||
|
associate attrs do |name, options|
|
||||||
|
HasManyReflection.new(name, options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [Array(Array<Symbol>, Hash{Symbol => Object})] attrs
|
||||||
|
# @return [void]
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# belongs_to :author, serializer: AuthorSerializer
|
||||||
|
#
|
||||||
|
def belongs_to(*attrs)
|
||||||
|
associate attrs do |name, options|
|
||||||
|
BelongsToReflection.new(name, options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [Array(Array<Symbol>, Hash{Symbol => Object})] attrs
|
||||||
|
# @return [void]
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# has_one :author, serializer: AuthorSerializer
|
||||||
|
#
|
||||||
|
def has_one(*attrs)
|
||||||
|
associate attrs do |name, options|
|
||||||
|
HasOneReflection.new(name, options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Add reflection and define {name} accessor.
|
||||||
|
# @param [Array<Symbol>]
|
||||||
|
# @yield [Symbol] return reflection
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def associate(attrs)
|
||||||
|
options = attrs.extract_options!
|
||||||
|
|
||||||
|
self._reflections = _reflections.dup
|
||||||
|
|
||||||
|
attrs.each do |name|
|
||||||
|
unless method_defined?(name)
|
||||||
|
define_method name do
|
||||||
|
object.send name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self._reflections << yield(name, options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [Enumerator<Association>]
|
||||||
|
#
|
||||||
|
def associations
|
||||||
|
return unless object
|
||||||
|
|
||||||
|
Enumerator.new do |y|
|
||||||
|
self.class._reflections.each do |reflection|
|
||||||
|
y.yield reflection.build_association(self, options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
10
lib/active_model/serializer/belongs_to_reflection.rb
Normal file
10
lib/active_model/serializer/belongs_to_reflection.rb
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module ActiveModel
|
||||||
|
class Serializer
|
||||||
|
# @api private
|
||||||
|
class BelongsToReflection < SingularReflection
|
||||||
|
def macro
|
||||||
|
:belongs_to
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
7
lib/active_model/serializer/collection_reflection.rb
Normal file
7
lib/active_model/serializer/collection_reflection.rb
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module ActiveModel
|
||||||
|
class Serializer
|
||||||
|
# @api private
|
||||||
|
class CollectionReflection < Reflection
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
10
lib/active_model/serializer/has_many_reflection.rb
Normal file
10
lib/active_model/serializer/has_many_reflection.rb
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module ActiveModel
|
||||||
|
class Serializer
|
||||||
|
# @api private
|
||||||
|
class HasManyReflection < CollectionReflection
|
||||||
|
def macro
|
||||||
|
:has_many
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
10
lib/active_model/serializer/has_one_reflection.rb
Normal file
10
lib/active_model/serializer/has_one_reflection.rb
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module ActiveModel
|
||||||
|
class Serializer
|
||||||
|
# @api private
|
||||||
|
class HasOneReflection < SingularReflection
|
||||||
|
def macro
|
||||||
|
:has_one
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
74
lib/active_model/serializer/reflection.rb
Normal file
74
lib/active_model/serializer/reflection.rb
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
module ActiveModel
|
||||||
|
class Serializer
|
||||||
|
# Holds all the meta-data about an association as it was specified in the
|
||||||
|
# ActiveModel::Serializer class.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# class PostSerializer < ActiveModel::Serializer
|
||||||
|
# has_one :author, serializer: AuthorSerializer
|
||||||
|
# has_many :comments
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# PostSerializer._reflections #=>
|
||||||
|
# # [
|
||||||
|
# # HasOneReflection.new(:author, serializer: AuthorSerializer),
|
||||||
|
# # HasManyReflection.new(:comments)
|
||||||
|
# # ]
|
||||||
|
#
|
||||||
|
# So you can inspect reflections in your Adapters.
|
||||||
|
#
|
||||||
|
Reflection = Struct.new(:name, :options) do
|
||||||
|
# Build association. This method is used internally to
|
||||||
|
# build serializer's association by its reflection.
|
||||||
|
#
|
||||||
|
# @param [Serializer] subject is a parent serializer for given association
|
||||||
|
# @param [Hash{Symbol => Object}] parent_serializer_options
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# # Given the following serializer defined:
|
||||||
|
# class PostSerializer < ActiveModel::Serializer
|
||||||
|
# has_many :comments, serializer: CommentSummarySerializer
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # Then you instantiate your serializer
|
||||||
|
# post_serializer = PostSerializer.new(post, foo: 'bar') #
|
||||||
|
# # to build association for comments you need to get reflection
|
||||||
|
# comments_reflection = PostSerializer._reflections.detect { |r| r.name == :comments }
|
||||||
|
# # and #build_association
|
||||||
|
# comments_reflection.build_association(post_serializer, foo: 'bar')
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def build_association(subject, parent_serializer_options)
|
||||||
|
association_value = subject.send(name)
|
||||||
|
reflection_options = options.dup
|
||||||
|
serializer_class = ActiveModel::Serializer.serializer_for(association_value, reflection_options)
|
||||||
|
|
||||||
|
if serializer_class
|
||||||
|
begin
|
||||||
|
serializer = serializer_class.new(
|
||||||
|
association_value,
|
||||||
|
serializer_options(parent_serializer_options, reflection_options)
|
||||||
|
)
|
||||||
|
rescue ActiveModel::Serializer::ArraySerializer::NoSerializerError
|
||||||
|
reflection_options[:virtual_value] = association_value.try(:as_json) || association_value
|
||||||
|
end
|
||||||
|
elsif !association_value.nil? && !association_value.instance_of?(Object)
|
||||||
|
reflection_options[:virtual_value] = association_value
|
||||||
|
end
|
||||||
|
|
||||||
|
Association.new(name, serializer, reflection_options)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def serializer_options(parent_serializer_options, reflection_options)
|
||||||
|
serializer = reflection_options.fetch(:serializer, nil)
|
||||||
|
|
||||||
|
serializer_options = parent_serializer_options.except(:serializer)
|
||||||
|
serializer_options[:serializer] = serializer if serializer
|
||||||
|
serializer_options
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
7
lib/active_model/serializer/singular_reflection.rb
Normal file
7
lib/active_model/serializer/singular_reflection.rb
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module ActiveModel
|
||||||
|
class Serializer
|
||||||
|
# @api private
|
||||||
|
class SingularReflection < Reflection
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
36
test/serializers/association_macros_test.rb
Normal file
36
test/serializers/association_macros_test.rb
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
module ActiveModel
|
||||||
|
class Serializer
|
||||||
|
class AssociationMacrosTest < Minitest::Test
|
||||||
|
AuthorSummarySerializer = Class.new
|
||||||
|
class AssociationsTestSerializer < Serializer
|
||||||
|
belongs_to :author, serializer: AuthorSummarySerializer
|
||||||
|
has_many :comments, embed: :ids
|
||||||
|
has_one :category
|
||||||
|
end
|
||||||
|
|
||||||
|
def before_setup
|
||||||
|
@reflections = AssociationsTestSerializer._reflections
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_has_one_defines_reflection
|
||||||
|
has_one_reflection = HasOneReflection.new(:category, {})
|
||||||
|
|
||||||
|
assert_includes(@reflections, has_one_reflection)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_has_many_defines_reflection
|
||||||
|
has_many_reflection = HasManyReflection.new(:comments, embed: :ids)
|
||||||
|
|
||||||
|
assert_includes(@reflections, has_many_reflection)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_belongs_to_defines_reflection
|
||||||
|
belongs_to_reflection = BelongsToReflection.new(:author, serializer: AuthorSummarySerializer)
|
||||||
|
|
||||||
|
assert_includes(@reflections, belongs_to_reflection)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -45,21 +45,20 @@ module ActiveModel
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_has_many_and_has_one
|
def test_has_many_and_has_one
|
||||||
assert_equal(
|
@author_serializer.associations.each do |association|
|
||||||
{ posts: { type: :has_many, association_options: { embed: :ids } },
|
key = association.key
|
||||||
roles: { type: :has_many, association_options: { embed: :ids } },
|
serializer = association.serializer
|
||||||
bio: { type: :has_one, association_options: {} } },
|
options = association.options
|
||||||
@author_serializer.class._associations
|
|
||||||
)
|
case key
|
||||||
@author_serializer.each_association do |key, serializer, options|
|
when :posts
|
||||||
if key == :posts
|
assert_equal({ embed: :ids }, options)
|
||||||
assert_equal({embed: :ids}, options)
|
|
||||||
assert_kind_of(ActiveModel::Serializer.config.array_serializer, serializer)
|
assert_kind_of(ActiveModel::Serializer.config.array_serializer, serializer)
|
||||||
elsif key == :bio
|
when :bio
|
||||||
assert_equal({}, options)
|
assert_equal({}, options)
|
||||||
assert_nil serializer
|
assert_nil serializer
|
||||||
elsif key == :roles
|
when :roles
|
||||||
assert_equal({embed: :ids}, options)
|
assert_equal({ embed: :ids }, options)
|
||||||
assert_kind_of(ActiveModel::Serializer.config.array_serializer, serializer)
|
assert_kind_of(ActiveModel::Serializer.config.array_serializer, serializer)
|
||||||
else
|
else
|
||||||
flunk "Unknown association: #{key}"
|
flunk "Unknown association: #{key}"
|
||||||
@ -68,7 +67,11 @@ module ActiveModel
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_has_many_with_no_serializer
|
def test_has_many_with_no_serializer
|
||||||
PostWithTagsSerializer.new(@post).each_association do |key, serializer, options|
|
PostWithTagsSerializer.new(@post).associations.each do |association|
|
||||||
|
key = association.key
|
||||||
|
serializer = association.serializer
|
||||||
|
options = association.options
|
||||||
|
|
||||||
assert_equal key, :tags
|
assert_equal key, :tags
|
||||||
assert_equal serializer, nil
|
assert_equal serializer, nil
|
||||||
assert_equal [{ attributes: { name: "#hashtagged" }}].to_json, options[:virtual_value].to_json
|
assert_equal [{ attributes: { name: "#hashtagged" }}].to_json, options[:virtual_value].to_json
|
||||||
@ -76,70 +79,67 @@ module ActiveModel
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_serializer_options_are_passed_into_associations_serializers
|
def test_serializer_options_are_passed_into_associations_serializers
|
||||||
@post_serializer.each_association do |key, association|
|
association = @post_serializer
|
||||||
if key == :comments
|
.associations
|
||||||
assert association.first.custom_options[:custom_options]
|
.detect { |association| association.key == :comments }
|
||||||
end
|
|
||||||
end
|
assert association.serializer.first.custom_options[:custom_options]
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_belongs_to
|
def test_belongs_to
|
||||||
assert_equal(
|
@comment_serializer.associations.each do |association|
|
||||||
{ post: { type: :belongs_to, association_options: {} },
|
key = association.key
|
||||||
author: { type: :belongs_to, association_options: {} } },
|
serializer = association.serializer
|
||||||
@comment_serializer.class._associations
|
|
||||||
)
|
case key
|
||||||
@comment_serializer.each_association do |key, serializer, options|
|
when :post
|
||||||
if key == :post
|
|
||||||
assert_equal({}, options)
|
|
||||||
assert_kind_of(PostSerializer, serializer)
|
assert_kind_of(PostSerializer, serializer)
|
||||||
elsif key == :author
|
when :author
|
||||||
assert_equal({}, options)
|
|
||||||
assert_nil serializer
|
assert_nil serializer
|
||||||
else
|
else
|
||||||
flunk "Unknown association: #{key}"
|
flunk "Unknown association: #{key}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
assert_equal({}, association.options)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_belongs_to_with_custom_method
|
def test_belongs_to_with_custom_method
|
||||||
blog_is_present = false
|
assert(
|
||||||
|
@post_serializer.associations.any? do |association|
|
||||||
@post_serializer.each_association do |key, serializer, options|
|
association.key == :blog
|
||||||
blog_is_present = true if key == :blog
|
end
|
||||||
end
|
)
|
||||||
|
|
||||||
assert blog_is_present
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_associations_inheritance
|
def test_associations_inheritance
|
||||||
inherited_klass = Class.new(PostSerializer)
|
inherited_klass = Class.new(PostSerializer)
|
||||||
|
|
||||||
assert_equal(PostSerializer._associations, inherited_klass._associations)
|
assert_equal(PostSerializer._reflections, inherited_klass._reflections)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_associations_inheritance_with_new_association
|
def test_associations_inheritance_with_new_association
|
||||||
inherited_klass = Class.new(PostSerializer) do
|
inherited_klass = Class.new(PostSerializer) do
|
||||||
has_many :top_comments, serializer: CommentSerializer
|
has_many :top_comments, serializer: CommentSerializer
|
||||||
end
|
end
|
||||||
expected_associations = PostSerializer._associations.merge(
|
|
||||||
top_comments: {
|
assert(
|
||||||
type: :has_many,
|
PostSerializer._reflections.all? do |reflection|
|
||||||
association_options: {
|
inherited_klass._reflections.include?(reflection)
|
||||||
serializer: CommentSerializer
|
end
|
||||||
}
|
)
|
||||||
}
|
|
||||||
|
assert(
|
||||||
|
inherited_klass._reflections.any? do |reflection|
|
||||||
|
reflection.name == :top_comments
|
||||||
|
end
|
||||||
)
|
)
|
||||||
assert_equal(inherited_klass._associations, expected_associations)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_associations_custom_keys
|
def test_associations_custom_keys
|
||||||
serializer = PostWithCustomKeysSerializer.new(@post)
|
serializer = PostWithCustomKeysSerializer.new(@post)
|
||||||
|
|
||||||
expected_association_keys = []
|
expected_association_keys = serializer.associations.map(&:key)
|
||||||
serializer.each_association do |key, serializer, options|
|
|
||||||
expected_association_keys << key
|
|
||||||
end
|
|
||||||
|
|
||||||
assert expected_association_keys.include? :reviews
|
assert expected_association_keys.include? :reviews
|
||||||
assert expected_association_keys.include? :writer
|
assert expected_association_keys.include? :writer
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user