From 7697d9f5ec292a483c7ca6adde99fcba1495ff83 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Sun, 23 Apr 2017 16:48:05 -0500 Subject: [PATCH] Refactor: introduce lazy association --- lib/active_model/serializer/association.rb | 37 ++++++++--- .../serializer/concerns/caching.rb | 7 +- .../serializer/lazy_association.rb | 22 +++++++ lib/active_model/serializer/reflection.rb | 6 +- .../adapter/json_api.rb | 2 +- .../adapter/json_api/relationship.rb | 8 +-- test/serializers/associations_test.rb | 65 ++++++++++--------- 7 files changed, 98 insertions(+), 49 deletions(-) create mode 100644 lib/active_model/serializer/lazy_association.rb diff --git a/lib/active_model/serializer/association.rb b/lib/active_model/serializer/association.rb index 459a8186..4be64529 100644 --- a/lib/active_model/serializer/association.rb +++ b/lib/active_model/serializer/association.rb @@ -1,3 +1,5 @@ +require 'active_model/serializer/lazy_association' + module ActiveModel class Serializer # This class holds all information about serializer's association. @@ -10,14 +12,22 @@ module ActiveModel # Association.new(:comments, { serializer: CommentSummarySerializer }) # 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] def key options.fetch(:key, name) end - # @return [ActiveModel::Serializer, nil] - def serializer - options[:serializer] + # @return [True,False] + def key? + options.key?(:key) end # @return [Hash] @@ -30,21 +40,30 @@ module ActiveModel options[:meta] end + def polymorphic? + true == 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, to: :lazy_association end end end diff --git a/lib/active_model/serializer/concerns/caching.rb b/lib/active_model/serializer/concerns/caching.rb index f4c72468..f633447a 100644 --- a/lib/active_model/serializer/concerns/caching.rb +++ b/lib/active_model/serializer/concerns/caching.rb @@ -193,12 +193,13 @@ module ActiveModel cache_keys << object_cache_key(serializer, adapter_instance) serializer.associations(include_directive).each do |association| - if association.serializer.respond_to?(:each) - association.serializer.each do |sub_serializer| + association_serializer = association.lazy_association.serializer + if association_serializer.respond_to?(:each) + association_serializer.each do |sub_serializer| cache_keys << object_cache_key(sub_serializer, adapter_instance) end else - cache_keys << object_cache_key(association.serializer, adapter_instance) + cache_keys << object_cache_key(association_serializer, adapter_instance) end end end diff --git a/lib/active_model/serializer/lazy_association.rb b/lib/active_model/serializer/lazy_association.rb new file mode 100644 index 00000000..c23e3c15 --- /dev/null +++ b/lib/active_model/serializer/lazy_association.rb @@ -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 diff --git a/lib/active_model/serializer/reflection.rb b/lib/active_model/serializer/reflection.rb index 12b7fcaf..32844962 100644 --- a/lib/active_model/serializer/reflection.rb +++ b/lib/active_model/serializer/reflection.rb @@ -170,7 +170,11 @@ module ActiveModel end 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 protected diff --git a/lib/active_model_serializers/adapter/json_api.rb b/lib/active_model_serializers/adapter/json_api.rb index 3d241e34..0e44ddd2 100644 --- a/lib/active_model_serializers/adapter/json_api.rb +++ b/lib/active_model_serializers/adapter/json_api.rb @@ -257,7 +257,7 @@ module ActiveModelSerializers def process_relationships(serializer, include_slice) 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 diff --git a/lib/active_model_serializers/adapter/json_api/relationship.rb b/lib/active_model_serializers/adapter/json_api/relationship.rb index 0d34cf93..e76172b4 100644 --- a/lib/active_model_serializers/adapter/json_api/relationship.rb +++ b/lib/active_model_serializers/adapter/json_api/relationship.rb @@ -15,9 +15,7 @@ module ActiveModelSerializers def as_json hash = {} - if association.options[:include_data] - hash[:data] = data_for(association) - end + hash[:data] = data_for(association) if association.include_data? links = links_for(association) hash[:links] = links if links.any? @@ -36,10 +34,10 @@ module ActiveModelSerializers private def data_for(association) - serializer = association.serializer + serializer = association.lazy_association.serializer if serializer.respond_to?(:each) 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 elsif serializer && serializer.object ResourceIdentifier.new(serializer, serializable_resource_options).as_json diff --git a/test/serializers/associations_test.rb b/test/serializers/associations_test.rb index 90d213dc..a76ddd92 100644 --- a/test/serializers/associations_test.rb +++ b/test/serializers/associations_test.rb @@ -30,18 +30,17 @@ module ActiveModel def test_has_many_and_has_one @author_serializer.associations.each do |association| key = association.key - serializer = association.serializer - options = association.options + serializer = association.lazy_association.serializer case key when :posts - assert_equal true, options.fetch(:include_data) + assert_equal true, association.include_data? assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer) when :bio - assert_equal true, options.fetch(:include_data) + assert_equal true, association.include_data? assert_nil serializer when :roles - assert_equal true, options.fetch(:include_data) + assert_equal true, association.include_data? assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer) else flunk "Unknown association: #{key}" @@ -56,12 +55,11 @@ module ActiveModel end post_serializer_class.new(@post).associations.each do |association| key = association.key - serializer = association.serializer - options = association.options + serializer = association.lazy_association.serializer assert_equal :tags, key 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 @@ -70,7 +68,7 @@ module ActiveModel .associations .detect { |assoc| assoc.key == :comments } - comment_serializer = association.serializer.first + comment_serializer = association.lazy_association.serializer.first class << comment_serializer def custom_options instance_options @@ -82,7 +80,7 @@ module ActiveModel def test_belongs_to @comment_serializer.associations.each do |association| key = association.key - serializer = association.serializer + serializer = association.lazy_association.serializer case key when :post @@ -93,7 +91,7 @@ module ActiveModel flunk "Unknown association: #{key}" end - assert_equal true, association.options.fetch(:include_data) + assert_equal true, association.include_data? end end @@ -203,11 +201,11 @@ module ActiveModel @post_serializer.associations.each do |association| case association.key when :comments - assert_instance_of(ResourceNamespace::CommentSerializer, association.serializer.first) + assert_instance_of(ResourceNamespace::CommentSerializer, association.lazy_association.serializer.first) when :author - assert_instance_of(ResourceNamespace::AuthorSerializer, association.serializer) + assert_instance_of(ResourceNamespace::AuthorSerializer, association.lazy_association.serializer) when :description - assert_instance_of(ResourceNamespace::DescriptionSerializer, association.serializer) + assert_instance_of(ResourceNamespace::DescriptionSerializer, association.lazy_association.serializer) else flunk "Unknown association: #{key}" end @@ -245,11 +243,11 @@ module ActiveModel @post_serializer.associations.each do |association| case association.key when :comments - assert_instance_of(PostSerializer::CommentSerializer, association.serializer.first) + assert_instance_of(PostSerializer::CommentSerializer, association.lazy_association.serializer.first) when :author - assert_instance_of(PostSerializer::AuthorSerializer, association.serializer) + assert_instance_of(PostSerializer::AuthorSerializer, association.lazy_association.serializer) when :description - assert_instance_of(PostSerializer::DescriptionSerializer, association.serializer) + assert_instance_of(PostSerializer::DescriptionSerializer, association.lazy_association.serializer) else flunk "Unknown association: #{key}" end @@ -260,7 +258,7 @@ module ActiveModel def test_conditional_associations model = Class.new(::Model) do attributes :true, :false - associations :association + associations :something end.new(true: true, false: false) scenarios = [ @@ -284,7 +282,7 @@ module ActiveModel scenarios.each do |s| serializer = Class.new(ActiveModel::Serializer) do - belongs_to :association, s[:options] + belongs_to :something, s[:options] def true true @@ -296,7 +294,7 @@ module ActiveModel end 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 @@ -341,8 +339,8 @@ module ActiveModel @author_serializer = AuthorSerializer.new(@author) @inherited_post_serializer = InheritedPostSerializer.new(@post) @inherited_author_serializer = InheritedAuthorSerializer.new(@author) - @author_associations = @author_serializer.associations.to_a - @inherited_author_associations = @inherited_author_serializer.associations.to_a + @author_associations = @author_serializer.associations.to_a.sort_by(&:name) + @inherited_author_associations = @inherited_author_serializer.associations.to_a.sort_by(&:name) @post_associations = @post_serializer.associations.to_a @inherited_post_associations = @inherited_post_serializer.associations.to_a end @@ -361,28 +359,35 @@ module ActiveModel test 'a serializer inheriting from another serializer can redefine has_many and has_one associations' do 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 [true, false, true], @inherited_author_associations.map(&:polymorphic?) + assert_equal [false, false, false], @author_associations.map(&:polymorphic?) end 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, :comments], @inherited_post_associations.map(&:name) - refute @post_associations.detect { |assoc| assoc.name == :author }.options.key?(:polymorphic) - assert_equal true, @inherited_post_associations.detect { |assoc| assoc.name == :author }.options.fetch(:polymorphic) + refute @post_associations.detect { |assoc| assoc.name == :author }.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 } - refute original_comment_assoc.options.key?(:key) - assert_equal :reviews, new_comments_assoc.options.fetch(:key) + refute original_comment_assoc.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 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 - 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) end end