Refactor: introduce lazy association

This commit is contained in:
Benjamin Fleischer 2017-04-23 16:48:05 -05:00
parent 34d55e4729
commit 7697d9f5ec
7 changed files with 98 additions and 49 deletions

View File

@ -1,3 +1,5 @@
require 'active_model/serializer/lazy_association'
module ActiveModel module ActiveModel
class Serializer class Serializer
# This class holds all information about serializer's association. # This class holds all information about serializer's association.
@ -10,14 +12,22 @@ module ActiveModel
# Association.new(:comments, { serializer: CommentSummarySerializer }) # Association.new(:comments, { serializer: CommentSummarySerializer })
# #
class Association < Field class Association < Field
attr_reader :lazy_association
delegate :include_data?, :virtual_value, to: :lazy_association
def initialize(*)
super
@lazy_association = LazyAssociation.new(name, options, block)
end
# @return [Symbol] # @return [Symbol]
def key def key
options.fetch(:key, name) options.fetch(:key, name)
end end
# @return [ActiveModel::Serializer, nil] # @return [True,False]
def serializer def key?
options[:serializer] options.key?(:key)
end end
# @return [Hash] # @return [Hash]
@ -30,21 +40,30 @@ module ActiveModel
options[:meta] options[:meta]
end end
def polymorphic?
true == options[:polymorphic]
end
# @api private # @api private
def serializable_hash(adapter_options, adapter_instance) def serializable_hash(adapter_options, adapter_instance)
return options[:virtual_value] if options[:virtual_value] association_serializer = lazy_association.serializer
object = serializer && serializer.object return virtual_value if virtual_value
return unless object 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 if polymorphic? && serialization
polymorphic_type = object.class.name.underscore polymorphic_type = association_object.class.name.underscore
serialization = { type: polymorphic_type, polymorphic_type.to_sym => serialization } serialization = { type: polymorphic_type, polymorphic_type.to_sym => serialization }
end end
serialization serialization
end end
private
delegate :reflection, to: :lazy_association
end end
end end
end end

View File

@ -193,12 +193,13 @@ module ActiveModel
cache_keys << object_cache_key(serializer, adapter_instance) cache_keys << object_cache_key(serializer, adapter_instance)
serializer.associations(include_directive).each do |association| serializer.associations(include_directive).each do |association|
if association.serializer.respond_to?(:each) association_serializer = association.lazy_association.serializer
association.serializer.each do |sub_serializer| if association_serializer.respond_to?(:each)
association_serializer.each do |sub_serializer|
cache_keys << object_cache_key(sub_serializer, adapter_instance) cache_keys << object_cache_key(sub_serializer, adapter_instance)
end end
else else
cache_keys << object_cache_key(association.serializer, adapter_instance) cache_keys << object_cache_key(association_serializer, adapter_instance)
end end
end end
end end

View File

@ -0,0 +1,22 @@
module ActiveModel
class Serializer
class LazyAssociation < Field
def serializer
options[:serializer]
end
def include_data?
options[:include_data]
end
def virtual_value
options[:virtual_value]
end
def reflection
options[:reflection]
end
end
end
end

View File

@ -170,7 +170,11 @@ module ActiveModel
end end
association_block = nil association_block = nil
Association.new(name, reflection_options, association_block) reflection_options[:reflection] = self
reflection_options[:parent_serializer] = parent_serializer
reflection_options[:parent_serializer_options] = parent_serializer_options
reflection_options[:include_slice] = include_slice
Association.new(name, reflection_options, block)
end end
protected protected

View File

@ -257,7 +257,7 @@ module ActiveModelSerializers
def process_relationships(serializer, include_slice) def process_relationships(serializer, include_slice)
serializer.associations(include_slice).each do |association| serializer.associations(include_slice).each do |association|
process_relationship(association.serializer, include_slice[association.key]) process_relationship(association.lazy_association.serializer, include_slice[association.key])
end end
end end

View File

@ -15,9 +15,7 @@ module ActiveModelSerializers
def as_json def as_json
hash = {} hash = {}
if association.options[:include_data] hash[:data] = data_for(association) if association.include_data?
hash[:data] = data_for(association)
end
links = links_for(association) links = links_for(association)
hash[:links] = links if links.any? hash[:links] = links if links.any?
@ -36,10 +34,10 @@ module ActiveModelSerializers
private private
def data_for(association) def data_for(association)
serializer = association.serializer serializer = association.lazy_association.serializer
if serializer.respond_to?(:each) if serializer.respond_to?(:each)
serializer.map { |s| ResourceIdentifier.new(s, serializable_resource_options).as_json } serializer.map { |s| ResourceIdentifier.new(s, serializable_resource_options).as_json }
elsif (virtual_value = association.options[:virtual_value]) elsif (virtual_value = association.virtual_value)
virtual_value virtual_value
elsif serializer && serializer.object elsif serializer && serializer.object
ResourceIdentifier.new(serializer, serializable_resource_options).as_json ResourceIdentifier.new(serializer, serializable_resource_options).as_json

View File

@ -30,18 +30,17 @@ module ActiveModel
def test_has_many_and_has_one def test_has_many_and_has_one
@author_serializer.associations.each do |association| @author_serializer.associations.each do |association|
key = association.key key = association.key
serializer = association.serializer serializer = association.lazy_association.serializer
options = association.options
case key case key
when :posts when :posts
assert_equal true, options.fetch(:include_data) assert_equal true, association.include_data?
assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer) assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer)
when :bio when :bio
assert_equal true, options.fetch(:include_data) assert_equal true, association.include_data?
assert_nil serializer assert_nil serializer
when :roles when :roles
assert_equal true, options.fetch(:include_data) assert_equal true, association.include_data?
assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer) assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer)
else else
flunk "Unknown association: #{key}" flunk "Unknown association: #{key}"
@ -56,12 +55,11 @@ module ActiveModel
end end
post_serializer_class.new(@post).associations.each do |association| post_serializer_class.new(@post).associations.each do |association|
key = association.key key = association.key
serializer = association.serializer serializer = association.lazy_association.serializer
options = association.options
assert_equal :tags, key assert_equal :tags, key
assert_nil serializer assert_nil serializer
assert_equal [{ id: 'tagid', name: '#hashtagged' }].to_json, options[:virtual_value].to_json assert_equal [{ id: 'tagid', name: '#hashtagged' }].to_json, association.virtual_value.to_json
end end
end end
@ -70,7 +68,7 @@ module ActiveModel
.associations .associations
.detect { |assoc| assoc.key == :comments } .detect { |assoc| assoc.key == :comments }
comment_serializer = association.serializer.first comment_serializer = association.lazy_association.serializer.first
class << comment_serializer class << comment_serializer
def custom_options def custom_options
instance_options instance_options
@ -82,7 +80,7 @@ module ActiveModel
def test_belongs_to def test_belongs_to
@comment_serializer.associations.each do |association| @comment_serializer.associations.each do |association|
key = association.key key = association.key
serializer = association.serializer serializer = association.lazy_association.serializer
case key case key
when :post when :post
@ -93,7 +91,7 @@ module ActiveModel
flunk "Unknown association: #{key}" flunk "Unknown association: #{key}"
end end
assert_equal true, association.options.fetch(:include_data) assert_equal true, association.include_data?
end end
end end
@ -203,11 +201,11 @@ module ActiveModel
@post_serializer.associations.each do |association| @post_serializer.associations.each do |association|
case association.key case association.key
when :comments when :comments
assert_instance_of(ResourceNamespace::CommentSerializer, association.serializer.first) assert_instance_of(ResourceNamespace::CommentSerializer, association.lazy_association.serializer.first)
when :author when :author
assert_instance_of(ResourceNamespace::AuthorSerializer, association.serializer) assert_instance_of(ResourceNamespace::AuthorSerializer, association.lazy_association.serializer)
when :description when :description
assert_instance_of(ResourceNamespace::DescriptionSerializer, association.serializer) assert_instance_of(ResourceNamespace::DescriptionSerializer, association.lazy_association.serializer)
else else
flunk "Unknown association: #{key}" flunk "Unknown association: #{key}"
end end
@ -245,11 +243,11 @@ module ActiveModel
@post_serializer.associations.each do |association| @post_serializer.associations.each do |association|
case association.key case association.key
when :comments when :comments
assert_instance_of(PostSerializer::CommentSerializer, association.serializer.first) assert_instance_of(PostSerializer::CommentSerializer, association.lazy_association.serializer.first)
when :author when :author
assert_instance_of(PostSerializer::AuthorSerializer, association.serializer) assert_instance_of(PostSerializer::AuthorSerializer, association.lazy_association.serializer)
when :description when :description
assert_instance_of(PostSerializer::DescriptionSerializer, association.serializer) assert_instance_of(PostSerializer::DescriptionSerializer, association.lazy_association.serializer)
else else
flunk "Unknown association: #{key}" flunk "Unknown association: #{key}"
end end
@ -260,7 +258,7 @@ module ActiveModel
def test_conditional_associations def test_conditional_associations
model = Class.new(::Model) do model = Class.new(::Model) do
attributes :true, :false attributes :true, :false
associations :association associations :something
end.new(true: true, false: false) end.new(true: true, false: false)
scenarios = [ scenarios = [
@ -284,7 +282,7 @@ module ActiveModel
scenarios.each do |s| scenarios.each do |s|
serializer = Class.new(ActiveModel::Serializer) do serializer = Class.new(ActiveModel::Serializer) do
belongs_to :association, s[:options] belongs_to :something, s[:options]
def true def true
true true
@ -296,7 +294,7 @@ module ActiveModel
end end
hash = serializable(model, serializer: serializer).serializable_hash hash = serializable(model, serializer: serializer).serializable_hash
assert_equal(s[:included], hash.key?(:association), "Error with #{s[:options]}") assert_equal(s[:included], hash.key?(:something), "Error with #{s[:options]}")
end end
end end
@ -341,8 +339,8 @@ module ActiveModel
@author_serializer = AuthorSerializer.new(@author) @author_serializer = AuthorSerializer.new(@author)
@inherited_post_serializer = InheritedPostSerializer.new(@post) @inherited_post_serializer = InheritedPostSerializer.new(@post)
@inherited_author_serializer = InheritedAuthorSerializer.new(@author) @inherited_author_serializer = InheritedAuthorSerializer.new(@author)
@author_associations = @author_serializer.associations.to_a @author_associations = @author_serializer.associations.to_a.sort_by(&:name)
@inherited_author_associations = @inherited_author_serializer.associations.to_a @inherited_author_associations = @inherited_author_serializer.associations.to_a.sort_by(&:name)
@post_associations = @post_serializer.associations.to_a @post_associations = @post_serializer.associations.to_a
@inherited_post_associations = @inherited_post_serializer.associations.to_a @inherited_post_associations = @inherited_post_serializer.associations.to_a
end end
@ -361,28 +359,35 @@ module ActiveModel
test 'a serializer inheriting from another serializer can redefine has_many and has_one associations' do test 'a serializer inheriting from another serializer can redefine has_many and has_one associations' do
expected = [:roles, :bio].sort expected = [:roles, :bio].sort
result = (@inherited_author_associations - @author_associations).map(&:name).sort result = (@inherited_author_associations.map(&:reflection) - @author_associations.map(&:reflection)).map(&:name)
assert_equal(result, expected) assert_equal(result, expected)
assert_equal [true, false, true], @inherited_author_associations.map(&:polymorphic?)
assert_equal [false, false, false], @author_associations.map(&:polymorphic?)
end end
test 'a serializer inheriting from another serializer can redefine belongs_to associations' do test 'a serializer inheriting from another serializer can redefine belongs_to associations' do
assert_equal [:author, :comments, :blog], @post_associations.map(&:name) assert_equal [:author, :comments, :blog], @post_associations.map(&:name)
assert_equal [:author, :comments, :blog, :comments], @inherited_post_associations.map(&:name) assert_equal [:author, :comments, :blog, :comments], @inherited_post_associations.map(&:name)
refute @post_associations.detect { |assoc| assoc.name == :author }.options.key?(:polymorphic) refute @post_associations.detect { |assoc| assoc.name == :author }.polymorphic?
assert_equal true, @inherited_post_associations.detect { |assoc| assoc.name == :author }.options.fetch(:polymorphic) assert @inherited_post_associations.detect { |assoc| assoc.name == :author }.polymorphic?
refute @post_associations.detect { |assoc| assoc.name == :comments }.options.key?(:key) refute @post_associations.detect { |assoc| assoc.name == :comments }.key?
original_comment_assoc, new_comments_assoc = @inherited_post_associations.select { |assoc| assoc.name == :comments } original_comment_assoc, new_comments_assoc = @inherited_post_associations.select { |assoc| assoc.name == :comments }
refute original_comment_assoc.options.key?(:key) refute original_comment_assoc.key?
assert_equal :reviews, new_comments_assoc.options.fetch(:key) assert_equal :reviews, new_comments_assoc.key
assert_equal @post_associations.detect { |assoc| assoc.name == :blog }, @inherited_post_associations.detect { |assoc| assoc.name == :blog } original_blog = @post_associations.detect { |assoc| assoc.name == :blog }
inherited_blog = @inherited_post_associations.detect { |assoc| assoc.name == :blog }
original_parent_serializer = original_blog.lazy_association.options.delete(:parent_serializer)
inherited_parent_serializer = inherited_blog.lazy_association.options.delete(:parent_serializer)
assert_equal PostSerializer, original_parent_serializer.class
assert_equal InheritedPostSerializer, inherited_parent_serializer.class
end end
test 'a serializer inheriting from another serializer can have an additional association with the same name but with different key' do test 'a serializer inheriting from another serializer can have an additional association with the same name but with different key' do
expected = [:author, :comments, :blog, :reviews].sort expected = [:author, :comments, :blog, :reviews].sort
result = @inherited_post_serializer.associations.map { |a| a.options.fetch(:key, a.name) }.sort result = @inherited_post_serializer.associations.map(&:key).sort
assert_equal(result, expected) assert_equal(result, expected)
end end
end end