Skip to content

Commit

Permalink
Bugfix: include nested has_many association
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
kurko committed Nov 11, 2014
1 parent 33e8d09 commit 971f501
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 5 deletions.
23 changes: 18 additions & 5 deletions lib/active_model/serializer/adapter/json_api.rb
Expand Up @@ -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|
Expand All @@ -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)
Expand Down
26 changes: 26 additions & 0 deletions test/action_controller/json_api_linked_test.rb
Expand Up @@ -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')
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions test/adapter/json_api/has_many_embed_ids_test.rb
Expand Up @@ -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]
Expand Down
1 change: 1 addition & 0 deletions test/adapter/json_api/linked_test.rb
Expand Up @@ -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])
Expand Down
8 changes: 8 additions & 0 deletions test/fixtures/poro.rb
Expand Up @@ -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
Expand All @@ -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

Expand Down
5 changes: 5 additions & 0 deletions test/serializers/associations_test.rb
Expand Up @@ -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]
Expand All @@ -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
)
Expand All @@ -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
Expand Down

0 comments on commit 971f501

Please sign in to comment.