Merge pull request #853 from mateomurphy/jsonapi-format-updates

RC3 Updates for JSON API
This commit is contained in:
Alexandre de Oliveira 2015-03-24 16:40:24 -03:00
commit db788a5f68
17 changed files with 403 additions and 237 deletions

View File

@ -2,4 +2,5 @@
* adds support for `meta` and `meta_key` [@kurko] * adds support for `meta` and `meta_key` [@kurko]
* adds method to override association [adcb99e, @kurko] * adds method to override association [adcb99e, @kurko]
* add `has_one` attribute for backwards compatibility [@ggordon] * adds `has_one` attribute for backwards compatibility [@ggordon]
* updates JSON API support to RC3 [@mateomurphy]

View File

@ -181,9 +181,9 @@ end
#### JSONAPI #### JSONAPI
This adapter follows the format specified in This adapter follows RC3 of the format specified in
[jsonapi.org/format](http://jsonapi.org/format). It will include the associated [jsonapi.org/format](http://jsonapi.org/format). It will include the associated
resources in the `"linked"` member when the resource names are included in the resources in the `"included"` member when the resource names are included in the
`include` option. `include` option.
```ruby ```ruby

View File

@ -164,6 +164,14 @@ module ActiveModel
end end
end end
def id
object.id if object
end
def type
object.class.to_s.demodulize.underscore.pluralize
end
def attributes(options = {}) def attributes(options = {})
attributes = attributes =
if options[:fields] if options[:fields]
@ -172,6 +180,8 @@ module ActiveModel
self.class._attributes.dup self.class._attributes.dup
end end
attributes += options[:required_fields] if options[:required_fields]
attributes.each_with_object({}) do |name, hash| attributes.each_with_object({}) do |name, hash|
hash[name] = send(name) hash[name] = send(name)
end end

View File

@ -16,16 +16,14 @@ module ActiveModel
end end
def serializable_hash(options = {}) def serializable_hash(options = {})
@root = (@options[:root] || serializer.json_key.to_s.pluralize).to_sym
if serializer.respond_to?(:each) if serializer.respond_to?(:each)
@hash[@root] = serializer.map do |s| @hash[:data] = serializer.map do |s|
self.class.new(s, @options.merge(top: @top, fieldset: @fieldset)).serializable_hash[@root] self.class.new(s, @options.merge(top: @top, fieldset: @fieldset)).serializable_hash[:data]
end end
else else
@hash = cached_object do @hash = cached_object do
@hash[@root] = attributes_for_serializer(serializer, @options) @hash[:data] = attributes_for_serializer(serializer, @options)
add_resource_links(@hash[@root], serializer) add_resource_links(@hash[:data], serializer)
@hash @hash
end end
end end
@ -35,58 +33,40 @@ module ActiveModel
private private
def add_links(resource, name, serializers) def add_links(resource, name, serializers)
type = serialized_object_type(serializers)
resource[:links] ||= {} resource[:links] ||= {}
resource[:links][name] ||= { linkage: [] }
if name.to_s == type || !type resource[:links][name][:linkage] += serializers.map { |serializer| { type: serializer.type, id: serializer.id.to_s } }
resource[:links][name] ||= []
resource[:links][name] += serializers.map{|serializer| serializer.id.to_s }
else
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 end
def add_link(resource, name, serializer) def add_link(resource, name, serializer)
resource[:links] ||= {} resource[:links] ||= {}
resource[:links][name] = nil resource[:links][name] = { linkage: nil }
if serializer && serializer.object if serializer && serializer.object
type = serialized_object_type(serializer) resource[:links][name][:linkage] = { type: serializer.type, id: serializer.id.to_s }
if name.to_s == type || !type
resource[:links][name] = serializer.id.to_s
else
resource[:links][name] ||= {}
resource[:links][name][:type] = type
resource[:links][name][:id] = serializer.id.to_s
end
end end
end end
def add_linked(resource_name, serializers, parent = nil) def add_included(resource_name, serializers, parent = nil)
serializers = Array(serializers) unless serializers.respond_to?(:each) serializers = Array(serializers) unless serializers.respond_to?(:each)
resource_path = [parent, resource_name].compact.join('.') resource_path = [parent, resource_name].compact.join('.')
if include_assoc?(resource_path) && resource_type = serialized_object_type(serializers) if include_assoc?(resource_path)
plural_name = resource_type.pluralize.to_sym @top[:included] ||= []
@top[:linked] ||= {}
@top[:linked][plural_name] ||= []
serializers.each do |serializer| serializers.each do |serializer|
attrs = attributes_for_serializer(serializer, @options) attrs = attributes_for_serializer(serializer, @options)
add_resource_links(attrs, serializer, add_linked: false) add_resource_links(attrs, serializer, add_included: false)
@top[:linked][plural_name].push(attrs) unless @top[:linked][plural_name].include?(attrs) @top[:included].push(attrs) unless @top[:included].include?(attrs)
end end
end end
serializers.each do |serializer| serializers.each do |serializer|
serializer.each_association do |name, association, opts| serializer.each_association do |name, association, opts|
add_linked(name, association, resource_path) if association add_included(name, association, resource_path) if association
end if include_nested_assoc? resource_path end if include_nested_assoc? resource_path
end end
end end
@ -97,14 +77,16 @@ module ActiveModel
result = [] result = []
serializer.each do |object| serializer.each do |object|
options[:fields] = @fieldset && @fieldset.fields_for(serializer) options[:fields] = @fieldset && @fieldset.fields_for(serializer)
options[:required_fields] = [:id, :type]
attributes = object.attributes(options) attributes = object.attributes(options)
attributes[:id] = attributes[:id].to_s if attributes[:id] attributes[:id] = attributes[:id].to_s
result << attributes result << attributes
end end
else else
options[:fields] = @fieldset && @fieldset.fields_for(serializer) options[:fields] = @fieldset && @fieldset.fields_for(serializer)
options[:required_fields] = [:id, :type]
result = serializer.attributes(options) result = serializer.attributes(options)
result[:id] = result[:id].to_s if result[:id] result[:id] = result[:id].to_s
end end
result result
@ -128,18 +110,8 @@ module ActiveModel
end end
end end
def serialized_object_type(serializer)
return false unless Array(serializer).first
type_name = Array(serializer).first.object.class.to_s.demodulize.underscore
if serializer.respond_to?(:first)
type_name.pluralize
else
type_name
end
end
def add_resource_links(attrs, serializer, options = {}) def add_resource_links(attrs, serializer, options = {})
options[:add_linked] = options.fetch(:add_linked, true) options[:add_included] = options.fetch(:add_included, true)
serializer.each_association do |name, association, opts| serializer.each_association do |name, association, opts|
attrs[:links] ||= {} attrs[:links] ||= {}
@ -150,9 +122,9 @@ module ActiveModel
add_link(attrs, name, association) add_link(attrs, name, association)
end end
if @options[:embed] != :ids && options[:add_linked] if options[:add_included]
Array(association).each do |association| Array(association).each do |association|
add_linked(name, association) add_included(name, association)
end end
end end
end end

View File

@ -29,7 +29,17 @@ module ActionController
def test_render_using_adapter_override def test_render_using_adapter_override
get :render_using_adapter_override get :render_using_adapter_override
assert_equal '{"profiles":{"name":"Name 1","description":"Description 1"}}', response.body
expected = {
data: {
name: "Name 1",
description: "Description 1",
id: assigns(:profile).id.to_s,
type: "profiles"
}
}
assert_equal expected.to_json, response.body
end end
def test_render_skipping_adapter def test_render_skipping_adapter

View File

@ -83,80 +83,87 @@ module ActionController
def test_render_resource_without_include def test_render_resource_without_include
get :render_resource_without_include get :render_resource_without_include
response = JSON.parse(@response.body) response = JSON.parse(@response.body)
refute response.key? 'linked' refute response.key? 'included'
end end
def test_render_resource_with_include def test_render_resource_with_include
get :render_resource_with_include get :render_resource_with_include
response = JSON.parse(@response.body) response = JSON.parse(@response.body)
assert response.key? 'linked' assert response.key? 'included'
assert_equal 1, response['linked']['authors'].size assert_equal 1, response['included'].size
assert_equal 'Steve K.', response['linked']['authors'].first['name'] assert_equal 'Steve K.', response['included'].first['name']
end end
def test_render_resource_with_nested_has_many_include def test_render_resource_with_nested_has_many_include
get :render_resource_with_nested_has_many_include get :render_resource_with_nested_has_many_include
response = JSON.parse(@response.body) response = JSON.parse(@response.body)
expected_linked = { expected_linked = [
"authors" => [{ {
"id" => "1", "id" => "1",
"type" => "authors",
"name" => "Steve K.", "name" => "Steve K.",
"links" => { "links" => {
"posts" => [], "posts" => { "linkage" => [] },
"roles" => ["1", "2"], "roles" => { "linkage" => [{ "type" =>"roles", "id" => "1" }, { "type" =>"roles", "id" => "2" }] },
"bio" => nil "bio" => { "linkage" => nil }
} }
}], }, {
"roles"=>[{
"id" => "1", "id" => "1",
"type" => "roles",
"name" => "admin", "name" => "admin",
"links" => { "links" => {
"author" => "1" "author" => { "linkage" => { "type" =>"authors", "id" => "1" } }
} }
}, { }, {
"id" => "2", "id" => "2",
"type" => "roles",
"name" => "colab", "name" => "colab",
"links" => { "links" => {
"author" => "1" "author" => { "linkage" => { "type" =>"authors", "id" => "1" } }
} }
}] }
} ]
assert_equal expected_linked, response['linked'] assert_equal expected_linked, response['included']
end end
def test_render_resource_with_nested_include def test_render_resource_with_nested_include
get :render_resource_with_nested_include get :render_resource_with_nested_include
response = JSON.parse(@response.body) response = JSON.parse(@response.body)
assert response.key? 'linked' assert response.key? 'included'
assert_equal 1, response['linked']['authors'].size assert_equal 1, response['included'].size
assert_equal 'Anonymous', response['linked']['authors'].first['name'] assert_equal 'Anonymous', response['included'].first['name']
end end
def test_render_collection_without_include def test_render_collection_without_include
get :render_collection_without_include get :render_collection_without_include
response = JSON.parse(@response.body) response = JSON.parse(@response.body)
refute response.key? 'linked' refute response.key? 'included'
end end
def test_render_collection_with_include def test_render_collection_with_include
get :render_collection_with_include get :render_collection_with_include
response = JSON.parse(@response.body) response = JSON.parse(@response.body)
assert response.key? 'linked' assert response.key? 'included'
end end
def test_render_resource_with_nested_attributes_even_when_missing_associations def test_render_resource_with_nested_attributes_even_when_missing_associations
get :render_resource_with_missing_nested_has_many_include get :render_resource_with_missing_nested_has_many_include
response = JSON.parse(@response.body) response = JSON.parse(@response.body)
assert response.key? 'linked' assert response.key? 'included'
refute response['linked'].key? 'roles' refute has_type?(response['included'], 'roles')
end end
def test_render_collection_with_missing_nested_has_many_include def test_render_collection_with_missing_nested_has_many_include
get :render_collection_with_missing_nested_has_many_include get :render_collection_with_missing_nested_has_many_include
response = JSON.parse(@response.body) response = JSON.parse(@response.body)
assert response.key? 'linked' assert response.key? 'included'
assert response['linked'].key? 'roles' assert has_type?(response['included'], 'roles')
end end
def has_type?(collection, value)
collection.detect { |i| i['type'] == value}
end
end end
end end
end end

View File

@ -2,8 +2,6 @@ require 'test_helper'
require 'pathname' require 'pathname'
class DefaultScopeNameTest < ActionController::TestCase class DefaultScopeNameTest < ActionController::TestCase
TestUser = Struct.new(:name, :admin)
class UserSerializer < ActiveModel::Serializer class UserSerializer < ActiveModel::Serializer
attributes :admin? attributes :admin?
def admin? def admin?
@ -17,11 +15,11 @@ class DefaultScopeNameTest < ActionController::TestCase
before_filter { request.format = :json } before_filter { request.format = :json }
def current_user def current_user
TestUser.new('Pete', false) User.new(id: 1, name: 'Pete', admin: false)
end end
def render_new_user def render_new_user
render json: TestUser.new('pete', false), serializer: UserSerializer, adapter: :json_api render json: User.new(id: 1, name: 'Pete', admin: false), serializer: UserSerializer, adapter: :json_api
end end
end end
@ -29,13 +27,11 @@ class DefaultScopeNameTest < ActionController::TestCase
def test_default_scope_name def test_default_scope_name
get :render_new_user get :render_new_user
assert_equal '{"users":{"admin?":false}}', @response.body assert_equal '{"data":{"admin?":false,"id":"1","type":"users"}}', @response.body
end end
end end
class SerializationScopeNameTest < ActionController::TestCase class SerializationScopeNameTest < ActionController::TestCase
TestUser = Struct.new(:name, :admin)
class AdminUserSerializer < ActiveModel::Serializer class AdminUserSerializer < ActiveModel::Serializer
attributes :admin? attributes :admin?
def admin? def admin?
@ -50,11 +46,11 @@ class SerializationScopeNameTest < ActionController::TestCase
before_filter { request.format = :json } before_filter { request.format = :json }
def current_admin def current_admin
TestUser.new('Bob', true) User.new(id: 2, name: 'Bob', admin: true)
end end
def render_new_user def render_new_user
render json: TestUser.new('pete', false), serializer: AdminUserSerializer, adapter: :json_api render json: User.new(id: 1, name: 'Pete', admin: false), serializer: AdminUserSerializer, adapter: :json_api
end end
end end
@ -62,6 +58,6 @@ class SerializationScopeNameTest < ActionController::TestCase
def test_override_scope_name_with_controller def test_override_scope_name_with_controller
get :render_new_user get :render_new_user
assert_equal '{"admin_users":{"admin?":true}}', @response.body assert_equal '{"data":{"admin?":true,"id":"1","type":"users"}}', @response.body
end end
end end

View File

@ -15,13 +15,11 @@ module ActionController
end end
def render_using_default_adapter_root def render_using_default_adapter_root
old_adapter = ActiveModel::Serializer.config.adapter with_adapter ActiveModel::Serializer::Adapter::JsonApi do
# JSON-API adapter sets root by default # JSON-API adapter sets root by default
ActiveModel::Serializer.config.adapter = ActiveModel::Serializer::Adapter::JsonApi @profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
@profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) render json: @profile
render json: @profile end
ensure
ActiveModel::Serializer.config.adapter = old_adapter
end end
def render_using_custom_root_in_adapter_with_a_default def render_using_custom_root_in_adapter_with_a_default
@ -39,15 +37,14 @@ module ActionController
end end
def render_array_using_implicit_serializer_and_meta def render_array_using_implicit_serializer_and_meta
old_adapter = ActiveModel::Serializer.config.adapter with_adapter ActiveModel::Serializer::Adapter::JsonApi do
ActiveModel::Serializer.config.adapter = ActiveModel::Serializer::Adapter::JsonApi @profiles = [
array = [ Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) ]
]
render json: array, meta: { total: 10 } render json: @profiles, meta: { total: 10 }
ensure end
ActiveModel::Serializer.config.adapter = old_adapter
end end
def render_object_with_cache_enabled def render_object_with_cache_enabled
@ -88,6 +85,15 @@ module ActionController
adapter = ActiveModel::Serializer.adapter.new(serializer) adapter = ActiveModel::Serializer.adapter.new(serializer)
adapter.to_json adapter.to_json
end end
def with_adapter(adapter)
old_adapter = ActiveModel::Serializer.config.adapter
# JSON-API adapter sets root by default
ActiveModel::Serializer.config.adapter = adapter
yield
ensure
ActiveModel::Serializer.config.adapter = old_adapter
end
end end
tests MyController tests MyController
@ -96,8 +102,13 @@ module ActionController
def test_render_using_implicit_serializer def test_render_using_implicit_serializer
get :render_using_implicit_serializer get :render_using_implicit_serializer
expected = {
name: "Name 1",
description: "Description 1"
}
assert_equal 'application/json', @response.content_type assert_equal 'application/json', @response.content_type
assert_equal '{"name":"Name 1","description":"Description 1"}', @response.body assert_equal expected.to_json, @response.body
end end
def test_render_using_custom_root def test_render_using_custom_root
@ -110,15 +121,33 @@ module ActionController
def test_render_using_default_root def test_render_using_default_root
get :render_using_default_adapter_root get :render_using_default_adapter_root
expected = {
data: {
name: "Name 1",
description: "Description 1",
id: assigns(:profile).id.to_s,
type: "profiles"
}
}
assert_equal 'application/json', @response.content_type assert_equal 'application/json', @response.content_type
assert_equal '{"profiles":{"name":"Name 1","description":"Description 1"}}', @response.body assert_equal expected.to_json, @response.body
end end
def test_render_using_custom_root_in_adapter_with_a_default def test_render_using_custom_root_in_adapter_with_a_default
get :render_using_custom_root_in_adapter_with_a_default get :render_using_custom_root_in_adapter_with_a_default
expected = {
data: {
name: "Name 1",
description: "Description 1",
id: assigns(:profile).id.to_s,
type: "profiles"
}
}
assert_equal 'application/json', @response.content_type assert_equal 'application/json', @response.content_type
assert_equal '{"profile":{"name":"Name 1","description":"Description 1"}}', @response.body assert_equal expected.to_json, @response.body
end end
def test_render_array_using_implicit_serializer def test_render_array_using_implicit_serializer
@ -142,8 +171,22 @@ module ActionController
def test_render_array_using_implicit_serializer_and_meta def test_render_array_using_implicit_serializer_and_meta
get :render_array_using_implicit_serializer_and_meta get :render_array_using_implicit_serializer_and_meta
expected = {
data: [
{
name: "Name 1",
description: "Description 1",
id: assigns(:profiles).first.id.to_s,
type: "profiles"
}
],
meta: {
total: 10
}
}
assert_equal 'application/json', @response.content_type assert_equal 'application/json', @response.content_type
assert_equal '{"profiles":[{"name":"Name 1","description":"Description 1"}],"meta":{"total":10}}', @response.body assert_equal expected.to_json, @response.body
end end
def test_render_with_cache_enable def test_render_with_cache_enable

View File

@ -32,56 +32,71 @@ module ActiveModel
end end
def test_includes_post_id def test_includes_post_id
assert_equal("42", @adapter.serializable_hash[:comments][:links][:post]) expected = { linkage: { type: "posts", id: "42" } }
assert_equal(expected, @adapter.serializable_hash[:data][:links][:post])
end end
def test_includes_linked_post def test_includes_linked_post
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'post') @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'post')
expected = [{ expected = [{
id: "42", id: "42",
type: "posts",
title: 'New Post', title: 'New Post',
body: 'Body', body: 'Body',
links: { links: {
comments: ["1"], comments: { linkage: [ { type: "comments", id: "1" } ] },
blog: "999", blog: { linkage: { type: "blogs", id: "999" } },
author: "1" author: { linkage: { type: "authors", id: "1" } }
} }
}] }]
assert_equal expected, @adapter.serializable_hash[:linked][:posts] assert_equal expected, @adapter.serializable_hash[:included]
end end
def test_limiting_linked_post_fields def test_limiting_linked_post_fields
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'post', fields: {post: [:title]}) @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'post', fields: {post: [:title]})
expected = [{ expected = [{
id: "42",
type: "posts",
title: 'New Post', title: 'New Post',
links: { links: {
comments: ["1"], comments: { linkage: [ { type: "comments", id: "1" } ] },
blog: "999", blog: { linkage: { type: "blogs", id: "999" } },
author: "1" author: { linkage: { type: "authors", id: "1" } }
} }
}] }]
assert_equal expected, @adapter.serializable_hash[:linked][:posts] assert_equal expected, @adapter.serializable_hash[:included]
end end
def test_include_nil_author def test_include_nil_author
serializer = PostSerializer.new(@anonymous_post) serializer = PostSerializer.new(@anonymous_post)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer)
assert_equal({comments: [], blog: "999", author: nil}, adapter.serializable_hash[:posts][:links]) assert_equal({comments: { linkage: [] }, blog: { linkage: { type: "blogs", id: "999" } }, author: { linkage: nil }}, adapter.serializable_hash[:data][:links])
end end
def test_include_type_for_association_when_different_than_name def test_include_type_for_association_when_different_than_name
serializer = BlogSerializer.new(@blog) serializer = BlogSerializer.new(@blog)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer)
links = adapter.serializable_hash[:blogs][:links] links = adapter.serializable_hash[:data][:links]
expected = { expected = {
writer: { writer: {
type: "author", linkage: {
id: "1" type: "authors",
id: "1"
}
}, },
articles: { articles: {
type: "posts", linkage: [
ids: ["42", "43"] {
type: "posts",
id: "42"
},
{
type: "posts",
id: "43"
}
]
} }
} }
assert_equal expected, links assert_equal expected, links
@ -90,37 +105,39 @@ module ActiveModel
def test_include_linked_resources_with_type_name def test_include_linked_resources_with_type_name
serializer = BlogSerializer.new(@blog) serializer = BlogSerializer.new(@blog)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer, include: ['writer', 'articles']) adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer, include: ['writer', 'articles'])
linked = adapter.serializable_hash[:linked] linked = adapter.serializable_hash[:included]
expected = { expected = [
authors: [{ {
id: "1", id: "1",
type: "authors",
name: "Steve K.", name: "Steve K.",
links: { links: {
posts: [], posts: { linkage: [] },
roles: [], roles: { linkage: [] },
bio: nil bio: { linkage: nil }
} }
}], },{
posts: [{ id: "42",
type: "posts",
title: "New Post", title: "New Post",
body: "Body", body: "Body",
id: "42",
links: { links: {
comments: ["1"], comments: { linkage: [ { type: "comments", id: "1" } ] },
blog: "999", blog: { linkage: { type: "blogs", id: "999" } },
author: "1" author: { linkage: { type: "authors", id: "1" } }
} }
}, { }, {
id: "43",
type: "posts",
title: "Hello!!", title: "Hello!!",
body: "Hello, world!!", body: "Hello, world!!",
id: "43",
links: { links: {
comments: [], comments: { linkage: [] },
blog: "999", blog: { linkage: { type: "blogs", id: "999" } },
author: nil author: { linkage: nil }
} }
}] }
} ]
assert_equal expected, linked assert_equal expected, linked
end end
end end

View File

@ -25,18 +25,61 @@ module ActiveModel
end end
def test_include_multiple_posts def test_include_multiple_posts
assert_equal([ expected = [
{ title: "Hello!!", body: "Hello, world!!", id: "1", links: { comments: [], blog: "999", author: "1" } }, {
{ title: "New Post", body: "Body", id: "2", links: { comments: [], blog: "999", author: "1" } } id: "1",
], @adapter.serializable_hash[:posts]) type: "posts",
title: "Hello!!",
body: "Hello, world!!",
links: {
comments: { linkage: [] },
blog: { linkage: { type: "blogs", id: "999" } },
author: { linkage: { type: "authors", id: "1" } }
}
},
{
id: "2",
type: "posts",
title: "New Post",
body: "Body",
links: {
comments: { linkage: [] },
blog: { linkage: { type: "blogs", id: "999" } },
author: { linkage: { type: "authors", id: "1" } }
}
}
]
assert_equal(expected, @adapter.serializable_hash[:data])
end end
def test_limiting_fields def test_limiting_fields
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, fields: ['title']) @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, fields: ['title'])
assert_equal([
{ title: "Hello!!", links: { comments: [], blog: "999", author: "1" } }, expected = [
{ title: "New Post", links: { comments: [], blog: "999", author: "1" } } {
], @adapter.serializable_hash[:posts]) id: "1",
type: "posts",
title: "Hello!!",
links: {
comments: { linkage: [] },
blog: { linkage: { type: "blogs", id: "999" } },
author: { linkage: { type: "authors", id: "1" } }
}
},
{
id: "2",
type: "posts",
title: "New Post",
links: {
comments: { linkage: [] },
blog: { linkage: { type: "blogs", id: "999" } },
author: { linkage: { type: "authors", id: "1" } }
}
}
]
assert_equal(expected, @adapter.serializable_hash[:data])
end end
end end

View File

@ -25,7 +25,14 @@ module ActiveModel
end end
def test_includes_comment_ids def test_includes_comment_ids
assert_equal(["1", "2"], @adapter.serializable_hash[:authors][:links][:posts]) expected = {
linkage: [
{ type: "posts", id: "1"},
{ type: "posts", id: "2"}
]
}
assert_equal(expected, @adapter.serializable_hash[:data][:links][:posts])
end end
def test_no_includes_linked_comments def test_no_includes_linked_comments

View File

@ -29,37 +29,67 @@ module ActiveModel
end end
def test_includes_comment_ids def test_includes_comment_ids
assert_equal(['1', '2'], expected = {
@adapter.serializable_hash[:posts][:links][:comments]) linkage: [
{ type: 'comments', id: '1' },
{ type: 'comments', id: '2' }
]
}
assert_equal(expected, @adapter.serializable_hash[:data][:links][:comments])
end end
def test_includes_linked_comments def test_includes_linked_data
# If CommentPreviewSerializer is applied correctly the body text will not be present in the output # If CommentPreviewSerializer is applied correctly the body text will not be present in the output
assert_equal([{ id: '1', links: { post: @post.id.to_s}}, expected = [
{ id: '2', links: { post: @post.id.to_s}}], {
@adapter.serializable_hash[:linked][:comments]) id: '1',
type: 'comments',
links: {
post: { linkage: { type: 'posts', id: @post.id.to_s } }
}
},
{
id: '2',
type: 'comments',
links: {
post: { linkage: { type: 'posts', id: @post.id.to_s } }
}
},
{
id: @author.id.to_s,
type: "authors",
links: {
posts: { linkage: [ {type: "posts", id: @post.id.to_s } ] }
}
}
]
assert_equal(expected, @adapter.serializable_hash[:included])
end end
def test_includes_author_id def test_includes_author_id
assert_equal(@author.id.to_s, expected = {
@adapter.serializable_hash[:posts][:links][:author]) linkage: { type: "authors", id: @author.id.to_s }
end }
def test_includes_linked_authors assert_equal(expected, @adapter.serializable_hash[:data][:links][:author])
assert_equal([{ id: @author.id.to_s, links: { posts: [@post.id.to_s] } }],
@adapter.serializable_hash[:linked][:authors])
end end
def test_explicit_serializer_with_null_resource def test_explicit_serializer_with_null_resource
@post.author = nil @post.author = nil
assert_equal(nil,
@adapter.serializable_hash[:posts][:links][:author]) expected = { linkage: nil }
assert_equal(expected, @adapter.serializable_hash[:data][:links][:author])
end end
def test_explicit_serializer_with_null_collection def test_explicit_serializer_with_null_collection
@post.comments = [] @post.comments = []
assert_equal([],
@adapter.serializable_hash[:posts][:links][:comments]) expected = { linkage: [] }
assert_equal(expected, @adapter.serializable_hash[:data][:links][:comments])
end end
end end
end end

View File

@ -33,45 +33,51 @@ module ActiveModel
end end
def test_includes_comment_ids def test_includes_comment_ids
assert_equal(["1", "2"], @adapter.serializable_hash[:posts][:links][:comments]) expected = { linkage: [ { type: "comments", id: "1" }, { type: "comments", id: "2" } ] }
assert_equal(expected, @adapter.serializable_hash[:data][:links][:comments])
end end
def test_includes_linked_comments def test_includes_linked_comments
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'comments') @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'comments')
expected = [{ expected = [{
id: "1", id: "1",
type: "comments",
body: 'ZOMG A COMMENT', body: 'ZOMG A COMMENT',
links: { links: {
post: "1", post: { linkage: { type: "posts", id: "1" } },
author: nil author: { linkage: nil }
} }
}, { }, {
id: "2", id: "2",
type: "comments",
body: 'ZOMG ANOTHER COMMENT', body: 'ZOMG ANOTHER COMMENT',
links: { links: {
post: "1", post: { linkage: { type: "posts", id: "1" } },
author: nil author: { linkage: nil }
} }
}] }]
assert_equal expected, @adapter.serializable_hash[:linked][:comments] assert_equal expected, @adapter.serializable_hash[:included]
end end
def test_limit_fields_of_linked_comments def test_limit_fields_of_linked_comments
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'comments', fields: {comment: [:id]}) @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'comments', fields: {comment: [:id]})
expected = [{ expected = [{
id: "1", id: "1",
type: "comments",
links: { links: {
post: "1", post: { linkage: { type: "posts", id: "1" } },
author: nil author: { linkage: nil }
} }
}, { }, {
id: "2", id: "2",
type: "comments",
links: { links: {
post: "1", post: { linkage: { type: "posts", id: "1" } },
author: nil author: { linkage: nil }
} }
}] }]
assert_equal expected, @adapter.serializable_hash[:linked][:comments] assert_equal expected, @adapter.serializable_hash[:included]
end end
def test_no_include_linked_if_comments_is_empty def test_no_include_linked_if_comments_is_empty
@ -84,10 +90,12 @@ module ActiveModel
def test_include_type_for_association_when_different_than_name def test_include_type_for_association_when_different_than_name
serializer = BlogSerializer.new(@blog) serializer = BlogSerializer.new(@blog)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer)
actual = adapter.serializable_hash[:blogs][:links][:articles] actual = adapter.serializable_hash[:data][:links][:articles]
expected = { expected = {
type: "posts", linkage: [{
ids: ["1"] type: "posts",
id: "1"
}]
} }
assert_equal expected, actual assert_equal expected, actual
end end

View File

@ -30,12 +30,26 @@ module ActiveModel
end end
def test_includes_bio_id def test_includes_bio_id
assert_equal("43", @adapter.serializable_hash[:authors][:links][:bio]) expected = { linkage: { type: "bios", id: "43" } }
assert_equal(expected, @adapter.serializable_hash[:data][:links][:bio])
end end
def test_includes_linked_bio def test_includes_linked_bio
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'bio') @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'bio')
assert_equal([{id: "43", :content=>"AMS Contributor", :links=>{:author=>"1"}}], @adapter.serializable_hash[:linked][:bios])
expected = [
{
id: "43",
type: "bios",
content:"AMS Contributor",
links: {
author: { linkage: { type: "authors", id: "1" } }
}
}
]
assert_equal(expected, @adapter.serializable_hash[:included])
end end
end end
end end

View File

@ -57,15 +57,15 @@ module ActiveModel
id: "1", id: "1",
body: "ZOMG A COMMENT", body: "ZOMG A COMMENT",
links: { links: {
post: "1", post: { linkage: { type: "posts", id: "1" } },
author: nil author: { linkage: nil }
} }
}, { }, {
id: "2", id: "2",
body: "ZOMG ANOTHER COMMENT", body: "ZOMG ANOTHER COMMENT",
links: { links: {
post: "1", post: { linkage: { type: "posts", id: "1" } },
author: nil author: { linkage: nil }
} }
} }
], ],
@ -74,17 +74,17 @@ module ActiveModel
id: "1", id: "1",
name: "Steve K.", name: "Steve K.",
links: { links: {
posts: ["1", "3"], posts: { linkage: [ { type: "posts", id: "1" }, { type: "posts", id: "3" } ] },
roles: [], roles: { linkage: [] },
bio: "1" bio: { linkage: { type: "bios", id: "1" } }
} }
}, { }, {
id: "2", id: "2",
name: "Tenderlove", name: "Tenderlove",
links: { links: {
posts: ["2"], posts: { linkage: [ { type: "posts", id:"2" } ] },
roles: [], roles: { linkage: [] },
bio: "2" bio: { linkage: { type: "bios", id: "2" } }
} }
} }
], ],
@ -93,13 +93,13 @@ module ActiveModel
id: "1", id: "1",
content: "AMS Contributor", content: "AMS Contributor",
links: { links: {
author: "1" author: { linkage: { type: "authors", id: "1" } }
} }
}, { }, {
id: "2", id: "2",
content: "Rails Contributor", content: "Rails Contributor",
links: { links: {
author: "2" author: { linkage: { type: "authors", id: "2" } }
} }
} }
] ]
@ -110,9 +110,9 @@ module ActiveModel
title: "Hello!!", title: "Hello!!",
body: "Hello, world!!", body: "Hello, world!!",
links: { links: {
comments: ['1', '2'], comments: { linkage: [ { type: "comments", id: '1' }, { type: "comments", id: '2' } ] },
blog: "999", blog: { linkage: { type: "blogs", id: "999" } },
author: "1" author: { linkage: { type: "authors", id: "1" } }
} }
}, },
{ {
@ -120,9 +120,9 @@ module ActiveModel
title: "New Post", title: "New Post",
body: "Body", body: "Body",
links: { links: {
comments: [], comments: { linkage: [] },
blog: "999", blog: { linkage: { type: "blogs", id: "999" } },
author: "2" author: { linkage: { type: "authors", id: "2" } }
} }
} }
] ]
@ -142,42 +142,41 @@ module ActiveModel
include: 'author,author.posts' include: 'author,author.posts'
) )
expected = { expected = [
authors: [ {
{ id: "1",
id: "1", type: "authors",
name: "Steve K.", name: "Steve K.",
links: { links: {
posts: ["10", "30"], posts: { linkage: [ { type: "posts", id: "10"}, { type: "posts", id: "30" }] },
roles: [], roles: { linkage: [] },
bio: "1" bio: { linkage: { type: "bios", id: "1" }}
}
} }
], }, {
posts: [ id: "10",
{ type: "posts",
id: "10", title: "Hello!!",
title: "Hello!!", body: "Hello, world!!",
body: "Hello, world!!", links: {
links: { comments: { linkage: [ { type: "comments", id: "1"}, { type: "comments", id: "2" }] },
comments: ["1", "2"], blog: { linkage: { type: "blogs", id: "999" } },
blog: "999", author: { linkage: { type: "authors", id: "1" } }
author: "1"
}
}, {
id: "30",
title: "Yet Another Post",
body: "Body",
links: {
comments: [],
blog: "999",
author: "1"
}
} }
] }, {
} id: "30",
assert_equal expected, adapter.serializable_hash[:linked] type: "posts",
assert_equal expected, alt_adapter.serializable_hash[:linked] title: "Yet Another Post",
body: "Body",
links: {
comments: { linkage: [] },
blog: { linkage: { type: "blogs", id: "999" } },
author: { linkage: { type: "authors", id: "1" } }
}
}
]
assert_equal expected, adapter.serializable_hash[:included]
assert_equal expected, alt_adapter.serializable_hash[:included]
end end
def test_ignore_model_namespace_for_linked_resource_type def test_ignore_model_namespace_for_linked_resource_type
@ -185,11 +184,13 @@ module ActiveModel
spammy_post.related = [Spam::UnrelatedLink.new(id: 456)] spammy_post.related = [Spam::UnrelatedLink.new(id: 456)]
serializer = SpammyPostSerializer.new(spammy_post) serializer = SpammyPostSerializer.new(spammy_post)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer)
links = adapter.serializable_hash[:posts][:links] links = adapter.serializable_hash[:data][:links]
expected = { expected = {
related: { related: {
type: 'unrelated_links', linkage: [{
ids: ['456'] type: 'unrelated_links',
id: '456'
}]
} }
} }
assert_equal expected, links assert_equal expected, links

View File

@ -63,6 +63,7 @@ Author = Class.new(Model)
Bio = Class.new(Model) Bio = Class.new(Model)
Blog = Class.new(Model) Blog = Class.new(Model)
Role = Class.new(Model) Role = Class.new(Model)
User = Class.new(Model)
module Spam; end module Spam; end
Spam::UnrelatedLink = Class.new(Model) Spam::UnrelatedLink = Class.new(Model)

View File

@ -15,7 +15,13 @@ module ActiveModel
def test_attributes_with_fields_option def test_attributes_with_fields_option
assert_equal({name: 'Name 1'}, assert_equal({name: 'Name 1'},
@profile_serializer.attributes( { fields: [:name] } ) ) @profile_serializer.attributes(fields: [:name]))
end
def test_required_fields
assert_equal({name: 'Name 1', description: 'Description 1'},
@profile_serializer.attributes(fields: [:name, :description], required_fields: [:name]))
end end
end end
end end