From 971f501e554a5b8ccfb1b082083ec02fe642e646 Mon Sep 17 00:00:00 2001 From: Alexandre de Oliveira Date: Tue, 11 Nov 2014 14:35:00 -0200 Subject: [PATCH] Bugfix: include nested has_many association Currently, doing `include: author.bio` would work correctly, but not for has_many associations such as `include: author.roles`. This fixes it. The problem was basically that we were not handling arrays for has_many linked, as happens for ArraySerializers. --- .../serializer/adapter/json_api.rb | 23 ++++++++++++---- .../action_controller/json_api_linked_test.rb | 26 +++++++++++++++++++ .../json_api/has_many_embed_ids_test.rb | 1 + test/adapter/json_api/linked_test.rb | 1 + test/fixtures/poro.rb | 8 ++++++ test/serializers/associations_test.rb | 5 ++++ 6 files changed, 59 insertions(+), 5 deletions(-) diff --git a/lib/active_model/serializer/adapter/json_api.rb b/lib/active_model/serializer/adapter/json_api.rb index 520e9d6ab..e1d2ea01f 100644 --- a/lib/active_model/serializer/adapter/json_api.rb +++ b/lib/active_model/serializer/adapter/json_api.rb @@ -77,10 +77,13 @@ def add_linked(resource, serializer, parent = nil) resource_path = [parent, resource].compact.join('.') if include_assoc? resource_path plural_name = resource.to_s.pluralize.to_sym - attrs = attributes_for_serializer(serializer, @options) + attrs = [attributes_for_serializer(serializer, @options)].flatten @top[:linked] ||= {} @top[:linked][plural_name] ||= [] - @top[:linked][plural_name].push attrs unless @top[:linked][plural_name].include? attrs + + attrs.each do |attrs| + @top[:linked][plural_name].push(attrs) unless @top[:linked][plural_name].include?(attrs) + end end serializer.each_association do |name, association, opts| @@ -91,9 +94,19 @@ def add_linked(resource, serializer, parent = nil) private def attributes_for_serializer(serializer, options) - attributes = serializer.attributes(options) - attributes[:id] = attributes[:id].to_s if attributes[:id] - attributes + if serializer.respond_to?(:each) + result = [] + serializer.each do |object| + attributes = object.attributes(options) + attributes[:id] = attributes[:id].to_s if attributes[:id] + result << attributes + end + else + result = serializer.attributes(options) + result[:id] = result[:id].to_s if result[:id] + end + + result end def include_assoc?(assoc) diff --git a/test/action_controller/json_api_linked_test.rb b/test/action_controller/json_api_linked_test.rb index 0770c78db..a5b7c8db6 100644 --- a/test/action_controller/json_api_linked_test.rb +++ b/test/action_controller/json_api_linked_test.rb @@ -5,12 +5,15 @@ module Serialization class JsonApiLinkedTest < ActionController::TestCase class MyController < ActionController::Base def setup_post + @role1 = Role.new(id: 1, name: 'admin') @author = Author.new(id: 1, name: 'Steve K.') @author.posts = [] @author.bio = nil + @author.roles = [@role1] @author2 = Author.new(id: 2, name: 'Anonymous') @author2.posts = [] @author2.bio = nil + @author2.roles = [] @post = Post.new(id: 1, title: 'New Post', body: 'Body') @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') @@ -51,6 +54,13 @@ def render_resource_with_nested_include end end + def render_resource_with_nested_has_many_include + with_json_api_adapter do + setup_post + render json: @post, include: 'author,author.roles' + end + end + def render_collection_without_include with_json_api_adapter do setup_post @@ -82,6 +92,22 @@ def test_render_resource_with_include assert_equal 'Steve K.', response['linked']['authors'].first['name'] end + def test_render_resource_with_nested_has_many_include + get :render_resource_with_nested_has_many_include + response = JSON.parse(@response.body) + expected_linked = { + "authors" => [{ + "id" => "1", + "name" => "Steve K." + }], + "roles"=>[{ + "id" => "1", + "name" => "admin" + }] + } + assert_equal expected_linked, response['linked'] + end + def test_render_resource_with_nested_include get :render_resource_with_nested_include response = JSON.parse(@response.body) diff --git a/test/adapter/json_api/has_many_embed_ids_test.rb b/test/adapter/json_api/has_many_embed_ids_test.rb index 8b3c02189..a6072aa4f 100644 --- a/test/adapter/json_api/has_many_embed_ids_test.rb +++ b/test/adapter/json_api/has_many_embed_ids_test.rb @@ -8,6 +8,7 @@ class HasManyEmbedIdsTest < Minitest::Test def setup @author = Author.new(name: 'Steve K.') @author.bio = nil + @author.roles = nil @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') @second_post = Post.new(id: 2, title: 'New Post', body: 'Body') @author.posts = [@first_post, @second_post] diff --git a/test/adapter/json_api/linked_test.rb b/test/adapter/json_api/linked_test.rb index 2808e969b..a79822479 100644 --- a/test/adapter/json_api/linked_test.rb +++ b/test/adapter/json_api/linked_test.rb @@ -16,6 +16,7 @@ def setup @second_post.author = @author @author.posts = [@first_post, @second_post] @author.bio = @bio + @author.roles = [] @bio.author = @author @serializer = ArraySerializer.new([@first_post, @second_post]) diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb index 07ce073e1..ae319a03d 100644 --- a/test/fixtures/poro.rb +++ b/test/fixtures/poro.rb @@ -40,6 +40,7 @@ class ProfileSerializer < ActiveModel::Serializer Author = Class.new(Model) Bio = Class.new(Model) Blog = Class.new(Model) +Role = Class.new(Model) PostSerializer = Class.new(ActiveModel::Serializer) do attributes :title, :body, :id @@ -60,9 +61,16 @@ class ProfileSerializer < ActiveModel::Serializer attributes :id, :name has_many :posts, embed: :ids + has_many :roles, embed: :ids belongs_to :bio end +RoleSerializer = Class.new(ActiveModel::Serializer) do + attributes :id, :name + + belongs_to :author +end + BioSerializer = Class.new(ActiveModel::Serializer) do attributes :id, :content diff --git a/test/serializers/associations_test.rb b/test/serializers/associations_test.rb index 651694969..b2278b450 100644 --- a/test/serializers/associations_test.rb +++ b/test/serializers/associations_test.rb @@ -27,6 +27,7 @@ def method_missing(meth, *args) def setup @author = Author.new(name: 'Steve K.') @author.bio = nil + @author.roles = [] @post = Post.new({ title: 'New Post', body: 'Body' }) @comment = Comment.new({ id: 1, body: 'ZOMG A COMMENT' }) @post.comments = [@comment] @@ -42,6 +43,7 @@ def setup def test_has_many assert_equal( { posts: { type: :has_many, options: { embed: :ids } }, + roles: { type: :has_many, options: { embed: :ids } }, bio: { type: :belongs_to, options: {} } }, @author_serializer.class._associations ) @@ -52,6 +54,9 @@ def test_has_many elsif name == :bio assert_equal({}, options) assert_nil serializer + elsif name == :roles + assert_equal({embed: :ids}, options) + assert_kind_of(ActiveModel::Serializer.config.array_serializer, serializer) else flunk "Unknown association: #{name}" end