diff --git a/lib/active_model/serializer/adapter/json_api.rb b/lib/active_model/serializer/adapter/json_api.rb index e1d2ea01..567630f8 100644 --- a/lib/active_model/serializer/adapter/json_api.rb +++ b/lib/active_model/serializer/adapter/json_api.rb @@ -19,69 +19,57 @@ module ActiveModel 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 + resource[:links][name] ||= {} + resource[:links][name][:type] = type + resource[:links][name][:id] = serializer.id.to_s end - - unless @options[:embed] == :ids - add_linked(name, serializer) - 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 @@ -91,8 +79,6 @@ module ActiveModel end if include_nested_assoc? resource_path end - private - def attributes_for_serializer(serializer, options) if serializer.respond_to?(:each) result = [] @@ -124,6 +110,36 @@ module ActiveModel 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 diff --git a/test/action_controller/json_api_linked_test.rb b/test/action_controller/json_api_linked_test.rb index a5b7c8db..d0138538 100644 --- a/test/action_controller/json_api_linked_test.rb +++ b/test/action_controller/json_api_linked_test.rb @@ -6,10 +6,13 @@ module ActionController 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 @@ -98,11 +101,25 @@ module ActionController 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'] diff --git a/test/adapter/json_api/belongs_to_test.rb b/test/adapter/json_api/belongs_to_test.rb index f18226af..5084dcc0 100644 --- a/test/adapter/json_api/belongs_to_test.rb +++ b/test/adapter/json_api/belongs_to_test.rb @@ -32,7 +32,16 @@ module ActiveModel 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 diff --git a/test/adapter/json_api/has_many_test.rb b/test/adapter/json_api/has_many_test.rb index 6ded298a..e6a511fb 100644 --- a/test/adapter/json_api/has_many_test.rb +++ b/test/adapter/json_api/has_many_test.rb @@ -35,10 +35,22 @@ module ActiveModel 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 diff --git a/test/adapter/json_api/linked_test.rb b/test/adapter/json_api/linked_test.rb index a7982247..d25cfa06 100644 --- a/test/adapter/json_api/linked_test.rb +++ b/test/adapter/json_api/linked_test.rb @@ -35,10 +35,42 @@ module ActiveModel { 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