active_model_serializers/lib/active_model/serializer/reflection.rb
Yohan Robert b1fd43303c Fix relationship behavior when using block
When using the relationships DSL with a block e.g.
has_one :bio do
  link :self, "some_link"
end
the "data" field would be rendered with a nil value even though the bio
is not nil. This happened because the block return value was set to nil
but used as a value for the "data" field.
2016-02-10 21:04:37 +01:00

134 lines
4.3 KiB
Ruby

require 'active_model/serializer/field'
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
# has_many :comments, key: :last_comments do
# object.comments.last(1)
# end
# has_many :secret_meta_data, if: :is_admin?
#
# def is_admin?
# current_user.admin?
# end
# end
#
# Specifically, the association 'comments' is evaluated two different ways:
# 1) as 'comments' and named 'comments'.
# 2) as 'object.comments.last(1)' and named 'last_comments'.
#
# PostSerializer._reflections #=>
# # [
# # HasOneReflection.new(:author, serializer: AuthorSerializer),
# # HasManyReflection.new(:comments)
# # HasManyReflection.new(:comments, { key: :last_comments }, #<Block>)
# # HasManyReflection.new(:secret_meta_data, { if: :is_admin? })
# # ]
#
# So you can inspect reflections in your Adapters.
#
class Reflection < Field
def initialize(*)
super
@_links = {}
@_include_data = true
end
def link(name, value = nil, &block)
@_links[name] = block || value
:nil
end
def meta(value = nil, &block)
@_meta = block || value
:nil
end
def include_data(value = true)
@_include_data = value
:nil
end
def value(serializer)
@object = serializer.object
@scope = serializer.scope
if block
block_value = instance_eval(&block)
if block_value == :nil
serializer.read_attribute_for_serialization(name)
else
block_value
end
else
serializer.read_attribute_for_serialization(name)
end
end
# 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 = value(subject)
reflection_options = options.dup
serializer_class = subject.class.serializer_for(association_value, reflection_options)
reflection_options[:include_data] = @_include_data
if serializer_class
begin
serializer = serializer_class.new(
association_value,
serializer_options(subject, parent_serializer_options, reflection_options)
)
rescue ActiveModel::Serializer::CollectionSerializer::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, @_links, @_meta)
end
protected
attr_accessor :object, :scope
private
def serializer_options(subject, 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[:serializer_context_class] = subject.class
serializer_options
end
end
end
end