Skip to content

Commit

Permalink
Merge pull request #727 from rails-api/include-links-in-linked-resources
Browse files Browse the repository at this point in the history
Includes links inside of linked resources
  • Loading branch information
kurko committed Nov 12, 2014
2 parents 7592e83 + 91b3fba commit 5038147
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 55 deletions.
102 changes: 59 additions & 43 deletions lib/active_model/serializer/adapter/json_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,69 +19,57 @@ def serializable_hash(options = {})
else
@hash[@root] = attributes_for_serializer(serializer, @options)

serializer.each_association do |name, association, opts|
@hash[@root][:links] ||= {}

if association.respond_to?(:each)
add_links(name, association, opts)
else
add_link(name, association, opts)
end
end
add_resource_links(@hash[@root], serializer)
end

@hash
end

def add_links(name, serializers, options)
if serializers.first
type = serializers.first.object.class.to_s.underscore.pluralize
end
private

def add_links(resource, name, serializers)
type = serialized_object_type(serializers)
resource[:links] ||= {}

if name.to_s == type || !type
@hash[@root][:links][name] ||= []
@hash[@root][:links][name] += serializers.map{|serializer| serializer.id.to_s }
resource[:links][name] ||= []
resource[:links][name] += serializers.map{|serializer| serializer.id.to_s }
else
@hash[@root][:links][name] ||= {}
@hash[@root][:links][name][:type] = type
@hash[@root][:links][name][:ids] ||= []
@hash[@root][:links][name][:ids] += serializers.map{|serializer| serializer.id.to_s }
end

unless serializers.none? || @options[:embed] == :ids
serializers.each do |serializer|
add_linked(name, serializer)
end
resource[:links][name] ||= {}
resource[:links][name][:type] = type
resource[:links][name][:ids] ||= []
resource[:links][name][:ids] += serializers.map{|serializer| serializer.id.to_s }
end
end

def add_link(name, serializer, options)
def add_link(resource, name, serializer)
resource[:links] ||= {}
resource[:links][name] = nil

if serializer
type = serializer.object.class.to_s.underscore
type = serialized_object_type(serializer)
if name.to_s == type || !type
@hash[@root][:links][name] = serializer.id.to_s
resource[:links][name] = serializer.id.to_s
else
@hash[@root][:links][name] ||= {}
@hash[@root][:links][name][:type] = type
@hash[@root][:links][name][:id] = serializer.id.to_s
end

unless @options[:embed] == :ids
add_linked(name, serializer)
resource[:links][name] ||= {}
resource[:links][name][:type] = type
resource[:links][name][:id] = serializer.id.to_s
end
else
@hash[@root][:links][name] = nil
end
end

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
def add_linked(resource_name, serializer, parent = nil)
resource_path = [parent, resource_name].compact.join('.')

if include_assoc?(resource_path)
plural_name = resource_name.to_s.pluralize.to_sym
attrs = [attributes_for_serializer(serializer, @options)].flatten
@top[:linked] ||= {}
@top[:linked][plural_name] ||= []

attrs.each do |attrs|
add_resource_links(attrs, serializer, add_linked: false)

@top[:linked][plural_name].push(attrs) unless @top[:linked][plural_name].include?(attrs)
end
end
Expand All @@ -91,8 +79,6 @@ def add_linked(resource, serializer, parent = nil)
end if include_nested_assoc? resource_path
end

private

def attributes_for_serializer(serializer, options)
if serializer.respond_to?(:each)
result = []
Expand Down Expand Up @@ -124,6 +110,36 @@ def check_assoc(assoc)
s.match(/^#{assoc.gsub('.', '\.')}/)
end
end

def serialized_object_type(serializer)
return false unless Array(serializer).first
type_name = Array(serializer).first.object.class.to_s.underscore
if serializer.respond_to?(:first)
type_name.pluralize
else
type_name
end
end

def add_resource_links(attrs, serializer, options = {})
options[:add_linked] = options.fetch(:add_linked, true)

Array(serializer).first.each_association do |name, association, opts|
attrs[:links] ||= {}

if association.respond_to?(:each)
add_links(attrs, name, association)
else
add_link(attrs, name, association)
end

if @options[:embed] != :ids && options[:add_linked]
Array(association).each do |association|
add_linked(name, association)
end
end
end
end
end
end
end
Expand Down
23 changes: 20 additions & 3 deletions test/action_controller/json_api_linked_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ class JsonApiLinkedTest < ActionController::TestCase
class MyController < ActionController::Base
def setup_post
@role1 = Role.new(id: 1, name: 'admin')
@role2 = Role.new(id: 2, name: 'colab')
@author = Author.new(id: 1, name: 'Steve K.')
@author.posts = []
@author.bio = nil
@author.roles = [@role1]
@author.roles = [@role1, @role2]
@role1.author = @author
@role2.author = @author
@author2 = Author.new(id: 2, name: 'Anonymous')
@author2.posts = []
@author2.bio = nil
Expand Down Expand Up @@ -98,11 +101,25 @@ def test_render_resource_with_nested_has_many_include
expected_linked = {
"authors" => [{
"id" => "1",
"name" => "Steve K."
"name" => "Steve K.",
"links" => {
"posts" => [],
"roles" => ["1", "2"],
"bio" => nil
}
}],
"roles"=>[{
"id" => "1",
"name" => "admin"
"name" => "admin",
"links" => {
"author" => "1"
}
}, {
"id" => "2",
"name" => "colab",
"links" => {
"author" => "1"
}
}]
}
assert_equal expected_linked, response['linked']
Expand Down
11 changes: 10 additions & 1 deletion test/adapter/json_api/belongs_to_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,16 @@ def test_includes_post_id

def test_includes_linked_post
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'post')
assert_equal([{id: "42", title: 'New Post', body: 'Body'}], @adapter.serializable_hash[:linked][:posts])
expected = [{
id: "42",
title: 'New Post',
body: 'Body',
links: {
comments: ["1"],
author: "1"
}
}]
assert_equal expected, @adapter.serializable_hash[:linked][:posts]
end

def test_include_nil_author
Expand Down
20 changes: 16 additions & 4 deletions test/adapter/json_api/has_many_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,22 @@ def test_includes_comment_ids

def test_includes_linked_comments
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'comments')
assert_equal([
{id: "1", body: 'ZOMG A COMMENT'},
{id: "2", body: 'ZOMG ANOTHER COMMENT'}
], @adapter.serializable_hash[:linked][:comments])
expected = [{
id: "1",
body: 'ZOMG A COMMENT',
links: {
post: "1",
author: nil
}
}, {
id: "2",
body: 'ZOMG ANOTHER COMMENT',
links: {
post: "1",
author: nil
}
}]
assert_equal expected, @adapter.serializable_hash[:linked][:comments]
end

def test_no_include_linked_if_comments_is_empty
Expand Down
40 changes: 36 additions & 4 deletions test/adapter/json_api/linked_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,42 @@ def test_include_multiple_posts_and_linked
{ title: "Hello!!", body: "Hello, world!!", id: "1", links: { comments: ['1', '2'], author: "1" } },
{ title: "New Post", body: "Body", id: "2", links: { comments: [], :author => "1" } }
], @adapter.serializable_hash[:posts])
assert_equal({ :comments => [{ :id => "1", :body => "ZOMG A COMMENT" },
{ :id => "2", :body => "ZOMG ANOTHER COMMENT" }],
:authors => [{ :id => "1", :name => "Steve K." }],
:bios=>[{:id=>"1", :content=>"AMS Contributor"}] }, @adapter.serializable_hash[:linked])


expected = {
comments: [{
id: "1",
body: "ZOMG A COMMENT",
links: {
post: "1",
author: nil
}
}, {
id: "2",
body: "ZOMG ANOTHER COMMENT",
links: {
post: "1",
author: nil
}
}],
authors: [{
id: "1",
name: "Steve K.",
links: {
posts: ["1", "2"],
roles: [],
bio: "1"
}
}],
bios: [{
id: "1",
content: "AMS Contributor",
links: {
author: "1"
}
}]
}
assert_equal expected, @adapter.serializable_hash[:linked]
end
end
end
Expand Down

0 comments on commit 5038147

Please sign in to comment.