forked from rails-api/active_model_serializers
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request rails-api#2026 from bf4/refactor_association
Refactor Association to make it eval reflection JIT
- Loading branch information
Showing
14 changed files
with
298 additions
and
193 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,67 @@ | ||
require 'active_model/serializer/lazy_association' | ||
|
||
module ActiveModel | ||
class Serializer | ||
# This class holds all information about serializer's association. | ||
# | ||
# @attr [Symbol] name | ||
# @attr [Hash{Symbol => Object}] options | ||
# @attr [block] | ||
# | ||
# @example | ||
# Association.new(:comments, { serializer: CommentSummarySerializer }) | ||
# | ||
class Association < Field | ||
# @api private | ||
Association = Struct.new(:reflection, :association_options) do | ||
attr_reader :lazy_association | ||
delegate :object, :include_data?, :virtual_value, :collection?, to: :lazy_association | ||
|
||
def initialize(*) | ||
super | ||
@lazy_association = LazyAssociation.new(reflection, association_options) | ||
end | ||
|
||
# @return [Symbol] | ||
delegate :name, to: :reflection | ||
|
||
# @return [Symbol] | ||
def key | ||
options.fetch(:key, name) | ||
reflection_options.fetch(:key, name) | ||
end | ||
|
||
# @return [ActiveModel::Serializer, nil] | ||
def serializer | ||
options[:serializer] | ||
# @return [True,False] | ||
def key? | ||
reflection_options.key?(:key) | ||
end | ||
|
||
# @return [Hash] | ||
def links | ||
options.fetch(:links) || {} | ||
reflection_options.fetch(:links) || {} | ||
end | ||
|
||
# @return [Hash, nil] | ||
# This gets mutated, so cannot use the cached reflection_options | ||
def meta | ||
options[:meta] | ||
reflection.options[:meta] | ||
end | ||
|
||
def polymorphic? | ||
true == reflection_options[:polymorphic] | ||
end | ||
|
||
# @api private | ||
def serializable_hash(adapter_options, adapter_instance) | ||
return options[:virtual_value] if options[:virtual_value] | ||
object = serializer && serializer.object | ||
return unless object | ||
association_serializer = lazy_association.serializer | ||
return virtual_value if virtual_value | ||
association_object = association_serializer && association_serializer.object | ||
return unless association_object | ||
|
||
serialization = serializer.serializable_hash(adapter_options, {}, adapter_instance) | ||
serialization = association_serializer.serializable_hash(adapter_options, {}, adapter_instance) | ||
|
||
if options[:polymorphic] && serialization | ||
polymorphic_type = object.class.name.underscore | ||
if polymorphic? && serialization | ||
polymorphic_type = association_object.class.name.underscore | ||
serialization = { type: polymorphic_type, polymorphic_type.to_sym => serialization } | ||
end | ||
|
||
serialization | ||
end | ||
|
||
private | ||
|
||
delegate :reflection_options, to: :lazy_association | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
module ActiveModel | ||
class Serializer | ||
# @api private | ||
class BelongsToReflection < SingularReflection | ||
class BelongsToReflection < Reflection | ||
end | ||
end | ||
end |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,10 @@ | ||
module ActiveModel | ||
class Serializer | ||
# @api private | ||
class HasManyReflection < CollectionReflection | ||
class HasManyReflection < Reflection | ||
def collection? | ||
true | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
module ActiveModel | ||
class Serializer | ||
# @api private | ||
class HasOneReflection < SingularReflection | ||
class HasOneReflection < Reflection | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
module ActiveModel | ||
class Serializer | ||
# @api private | ||
LazyAssociation = Struct.new(:reflection, :association_options) do | ||
REFLECTION_OPTIONS = %i(key links polymorphic meta serializer virtual_value namespace).freeze | ||
|
||
delegate :collection?, to: :reflection | ||
|
||
def reflection_options | ||
@reflection_options ||= reflection.options.dup.reject { |k, _| !REFLECTION_OPTIONS.include?(k) } | ||
end | ||
|
||
def object | ||
@object ||= reflection.value( | ||
association_options.fetch(:parent_serializer), | ||
association_options.fetch(:include_slice) | ||
) | ||
end | ||
alias_method :eval_reflection_block, :object | ||
|
||
def include_data? | ||
eval_reflection_block if reflection.block | ||
reflection.include_data?( | ||
association_options.fetch(:include_slice) | ||
) | ||
end | ||
|
||
# @return [ActiveModel::Serializer, nil] | ||
def serializer | ||
return @serializer if defined?(@serializer) | ||
if serializer_class | ||
serialize_object!(object) | ||
elsif !object.nil? && !object.instance_of?(Object) | ||
cached_result[:virtual_value] = object | ||
end | ||
@serializer = cached_result[:serializer] | ||
end | ||
|
||
def virtual_value | ||
cached_result[:virtual_value] || reflection_options[:virtual_value] | ||
end | ||
|
||
def serializer_class | ||
return @serializer_class if defined?(@serializer_class) | ||
serializer_for_options = { namespace: namespace } | ||
serializer_for_options[:serializer] = reflection_options[:serializer] if reflection_options.key?(:serializer) | ||
@serializer_class = association_options.fetch(:parent_serializer).class.serializer_for(object, serializer_for_options) | ||
end | ||
|
||
private | ||
|
||
def cached_result | ||
@cached_result ||= {} | ||
end | ||
|
||
def serialize_object!(object) | ||
if collection? | ||
if (serializer = instantiate_collection_serializer(object)).nil? | ||
# BUG: per #2027, JSON API resource relationships are only id and type, and hence either | ||
# *require* a serializer or we need to be a little clever about figuring out the id/type. | ||
# In either case, returning the raw virtual value will almost always be incorrect. | ||
# | ||
# Should be reflection_options[:virtual_value] or adapter needs to figure out what to do | ||
# with an object that is non-nil and has no defined serializer. | ||
cached_result[:virtual_value] = object.try(:as_json) || object | ||
else | ||
cached_result[:serializer] = serializer | ||
end | ||
else | ||
cached_result[:serializer] = instantiate_serializer(object) | ||
end | ||
end | ||
|
||
def instantiate_serializer(object) | ||
serializer_options = association_options.fetch(:parent_serializer_options).except(:serializer) | ||
serializer_options[:serializer_context_class] = association_options.fetch(:parent_serializer).class | ||
serializer = reflection_options.fetch(:serializer, nil) | ||
serializer_options[:serializer] = serializer if serializer | ||
serializer_class.new(object, serializer_options) | ||
end | ||
|
||
def instantiate_collection_serializer(object) | ||
serializer = catch(:no_serializer) do | ||
instantiate_serializer(object) | ||
end | ||
serializer | ||
end | ||
|
||
def namespace | ||
reflection_options[:namespace] || | ||
association_options.fetch(:parent_serializer_options)[:namespace] | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.