merge upstream update fieldset

This commit is contained in:
Aaron Renoir 2014-11-13 17:45:47 -08:00
commit 2ed52f96a6
22 changed files with 623 additions and 148 deletions

View File

@ -1,4 +1,7 @@
language: ruby language: ruby
sudo: false
rvm: rvm:
- 1.9.3 - 1.9.3
- 2.0.0 - 2.0.0
@ -6,13 +9,16 @@ rvm:
- jruby-19mode - jruby-19mode
- rbx-2 - rbx-2
- ruby-head - ruby-head
install: install:
- bundle install --retry=3 - bundle install --retry=3
env: env:
- "RAILS_VERSION=3.2" - "RAILS_VERSION=3.2"
- "RAILS_VERSION=4.0" - "RAILS_VERSION=4.0"
- "RAILS_VERSION=4.1" - "RAILS_VERSION=4.1"
- "RAILS_VERSION=master" - "RAILS_VERSION=master"
matrix: matrix:
allow_failures: allow_failures:
- rvm: ruby-head - rvm: ruby-head

View File

@ -69,6 +69,18 @@ ActiveModel::Serializer.config.adapter = :hal
You won't need to implement an adapter unless you wish to use a new format or You won't need to implement an adapter unless you wish to use a new format or
media type with AMS. media type with AMS.
If you would like the key in the outputted JSON to be different from its name in ActiveRecord, you can use the :key option to customize it:
```ruby
class PostSerializer < ActiveModel::Serializer
attributes :id, :body
# look up :subject on the model, but use +title+ in the JSON
attribute :subject, :key => :title
has_many :comments
end
```
In your controllers, when you use `render :json`, Rails will now first search In your controllers, when you use `render :json`, Rails will now first search
for a serializer for the object and use it if available. for a serializer for the object and use it if available.
@ -94,6 +106,27 @@ member when the resource names are included in the `include` option.
render @posts, include: 'authors,comments' render @posts, include: 'authors,comments'
``` ```
### Specify a serializer
If you wish to use a serializer other than the default, you can explicitly pass it to the renderer.
#### 1. For a resource:
```ruby
render json: @post, serializer: PostPreviewSerializer
```
#### 2. For an array resource:
```ruby
# Use the default `ArraySerializer`, which will use `each_serializer` to
# serialize each element
render json: @posts, each_serializer: PostPreviewSerializer
# Or, you can explicitly provide the collection serializer as well
render json: @posts, serializer: PaginatedSerializer, each_serializer: PostPreviewSerializer
```
## Installation ## Installation
Add this line to your application's Gemfile: Add this line to your application's Gemfile:

View File

@ -6,18 +6,32 @@ module ActionController
include ActionController::Renderers include ActionController::Renderers
ADAPTER_OPTION_KEYS = [:include, :fields, :root] ADAPTER_OPTION_KEYS = [:include, :fields, :root, :adapter]
def get_serializer(resource)
@_serializer ||= @_serializer_opts.delete(:serializer)
@_serializer ||= ActiveModel::Serializer.serializer_for(resource)
if @_serializer_opts.key?(:each_serializer)
@_serializer_opts[:serializer] = @_serializer_opts.delete(:each_serializer)
end
@_serializer
end
def use_adapter?
!(@_adapter_opts.key?(:adapter) && !@_adapter_opts[:adapter])
end
[:_render_option_json, :_render_with_renderer_json].each do |renderer_method| [:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
define_method renderer_method do |resource, options| define_method renderer_method do |resource, options|
serializer = ActiveModel::Serializer.serializer_for(resource) @_adapter_opts, @_serializer_opts =
options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] }
if serializer if use_adapter? && (serializer = get_serializer(resource))
adapter_opts, serializer_opts =
options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }
# omg hax # omg hax
object = serializer.new(resource, Hash[serializer_opts]) object = serializer.new(resource, @_serializer_opts)
adapter = ActiveModel::Serializer.adapter.new(object, Hash[adapter_opts]) adapter = ActiveModel::Serializer::Adapter.create(object, @_adapter_opts)
super(adapter, options) super(adapter, options)
else else
super(resource, options) super(resource, options)

View File

@ -21,7 +21,6 @@ module ActiveModel
def self.attributes(*attrs) def self.attributes(*attrs)
@_attributes.concat attrs @_attributes.concat attrs
attrs.each do |attr| attrs.each do |attr|
define_method attr do define_method attr do
object.read_attribute_for_serialization(attr) object.read_attribute_for_serialization(attr)
@ -29,6 +28,14 @@ module ActiveModel
end end
end end
def self.attribute(attr, options = {})
key = options.fetch(:key, attr)
@_attributes.concat [key]
define_method key do
object.read_attribute_for_serialization(attr)
end unless method_defined?(key)
end
# Defines an association in the object should be rendered. # Defines an association in the object should be rendered.
# #
# The serializer object should implement the association name # The serializer object should implement the association name
@ -83,8 +90,7 @@ module ActiveModel
def self.adapter def self.adapter
adapter_class = case config.adapter adapter_class = case config.adapter
when Symbol when Symbol
class_name = "ActiveModel::Serializer::Adapter::#{config.adapter.to_s.classify}" ActiveModel::Serializer::Adapter.adapter_class(config.adapter)
class_name.safe_constantize
when Class when Class
config.adapter config.adapter
end end

View File

@ -20,6 +20,16 @@ module ActiveModel
def as_json(options = {}) def as_json(options = {})
serializable_hash(options) serializable_hash(options)
end end
def self.create(resource, options = {})
override = options.delete(:adapter)
klass = override ? adapter_class(override) : ActiveModel::Serializer.adapter
klass.new(resource, options)
end
def self.adapter_class(adapter)
"ActiveModel::Serializer::Adapter::#{adapter.to_s.classify}".safe_constantize
end
end end
end end
end end

View File

@ -25,91 +25,129 @@ module ActiveModel
else else
@hash[@root] = attributes_for_serializer(serializer, @options) @hash[@root] = attributes_for_serializer(serializer, @options)
serializer.each_association do |name, association, opts| add_resource_links(@hash[@root], serializer)
@hash[@root][:links] ||= {}
if association.respond_to?(:each)
add_links(name, association, opts)
else
add_link(name, association, opts)
end
end
end end
@hash @hash
end end
def add_links(name, serializers, options)
if serializers.first
type = serializers.first.object.class.to_s.underscore.pluralize
end
if name.to_s == type || !type
@hash[@root][:links][name] ||= []
@hash[@root][: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
end
end
def add_link(name, serializer, options)
if serializer
type = serializer.object.class.to_s.underscore
if name.to_s == type || !type
@hash[@root][: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)
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
attrs = attributes_for_serializer(serializer, @options)
@top[:linked] ||= {}
@top[:linked][plural_name] ||= []
@top[:linked][plural_name].push attrs unless @top[:linked][plural_name].include? attrs
end
unless serializer.respond_to?(:each)
serializer.each_association do |name, association, opts|
add_linked(name, association, resource) if association
end
end
end
private private
def attributes_for_serializer(serializer, options) def add_links(resource, name, serializers)
if fields = @fieldset && @fieldset.fields_for(serializer) type = serialized_object_type(serializers)
options[:fields] = fields resource[:links] ||= {}
end
attributes = serializer.attributes(options) if name.to_s == type || !type
attributes[:id] = attributes[:id].to_s if attributes[:id] resource[:links][name] ||= []
attributes 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 include_assoc? assoc def add_link(resource, name, serializer)
@options[:include] && @options[:include].split(',').include?(assoc.to_s) resource[:links] ||= {}
resource[:links][name] = nil
if serializer
type = serialized_object_type(serializer)
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
def add_linked(resource_name, serializer, parent = nil)
resource_path = [parent, resource_name].compact.join('.')
if include_assoc?(resource_path)
plural_name = serialized_object_type(serializer).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
serializer.each_association do |name, association, opts|
add_linked(name, association, resource_path) if association
end if include_nested_assoc? resource_path
end
def attributes_for_serializer(serializer, options)
if serializer.respond_to?(:each)
result = []
serializer.each do |object|
options[:fields] = @fieldset && @fieldset.fields_for(serializer)
attributes = object.attributes(options)
attributes[:id] = attributes[:id].to_s if attributes[:id]
result << attributes
end
else
options[:fields] = @fieldset && @fieldset.fields_for(serializer)
result = serializer.attributes(options)
result[:id] = result[:id].to_s if result[:id]
end
result
end
def include_assoc?(assoc)
return false unless @options[:include]
check_assoc("#{assoc}$")
end
def include_nested_assoc?(assoc)
return false unless @options[:include]
check_assoc("#{assoc}.")
end
def check_assoc(assoc)
@options[:include].split(',').any? do |s|
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
end end

View File

@ -6,7 +6,10 @@ module ActiveModel
def initialize(objects, options = {}) def initialize(objects, options = {})
@objects = objects.map do |object| @objects = objects.map do |object|
serializer_class = ActiveModel::Serializer.serializer_for(object) serializer_class = options.fetch(
:serializer,
ActiveModel::Serializer.serializer_for(object)
)
serializer_class.new(object) serializer_class.new(object)
end end
end end

View File

@ -2,11 +2,13 @@ module ActiveModel
class Serializer class Serializer
class Fieldset class Fieldset
attr_reader :fields, :root
def initialize(fields, root = nil) def initialize(fields, root = nil)
@root = root @root = root
@fields = parse(fields) @raw_fields = fields
end
def fields
@fields ||= parsed_fields
end end
def fields_for(serializer) def fields_for(serializer)
@ -16,15 +18,17 @@ module ActiveModel
private private
def parse(fields) attr_reader :raw_fields, :root
if fields.is_a?(Hash)
fields.inject({}) { |h,(k,v)| h[k.to_sym] = v.map(&:to_sym); h} def parsed_fields
elsif fields.is_a?(Array) if raw_fields.is_a?(Hash)
raw_fields.inject({}) { |h,(k,v)| h[k.to_sym] = v.map(&:to_sym); h}
elsif raw_fields.is_a?(Array)
if root.nil? if root.nil?
raise ArgumentError, 'The root argument must be specified if the fileds argument is an array.' raise ArgumentError, 'The root argument must be specified if the fileds argument is an array.'
end end
hash = {} hash = {}
hash[root.to_sym] = fields.map(&:to_sym) hash[root.to_sym] = raw_fields.map(&:to_sym)
hash hash
else else
{} {}

View File

@ -0,0 +1,41 @@
require 'test_helper'
module ActionController
module Serialization
class AdapterSelectorTest < ActionController::TestCase
class MyController < ActionController::Base
def render_using_default_adapter
@profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
render json: @profile
end
def render_using_adapter_override
@profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
render json: @profile, adapter: :json_api
end
def render_skipping_adapter
@profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
render json: @profile, adapter: false
end
end
tests MyController
def test_render_using_default_adapter
get :render_using_default_adapter
assert_equal '{"name":"Name 1","description":"Description 1"}', response.body
end
def test_render_using_adapter_override
get :render_using_adapter_override
assert_equal '{"profiles":{"name":"Name 1","description":"Description 1"}}', response.body
end
def test_render_skipping_adapter
get :render_skipping_adapter
assert_equal '{"attributes":{"name":"Name 1","description":"Description 1","comments":"Comments 1"}}', response.body
end
end
end
end

View File

@ -0,0 +1,78 @@
require 'test_helper'
module ActionController
module Serialization
class ExplicitSerializerTest < ActionController::TestCase
class MyController < ActionController::Base
def render_using_explicit_serializer
@profile = Profile.new(name: 'Name 1',
description: 'Description 1',
comments: 'Comments 1')
render json: @profile, serializer: ProfilePreviewSerializer
end
def render_array_using_explicit_serializer
array = [
Profile.new(name: 'Name 1',
description: 'Description 1',
comments: 'Comments 1'),
Profile.new(name: 'Name 2',
description: 'Description 2',
comments: 'Comments 2')
]
render json: array,
serializer: PaginatedSerializer,
each_serializer: ProfilePreviewSerializer
end
def render_array_using_implicit_serializer
array = [
Profile.new(name: 'Name 1',
description: 'Description 1',
comments: 'Comments 1'),
Profile.new(name: 'Name 2',
description: 'Description 2',
comments: 'Comments 2')
]
render json: array,
each_serializer: ProfilePreviewSerializer
end
end
tests MyController
def test_render_using_explicit_serializer
get :render_using_explicit_serializer
assert_equal 'application/json', @response.content_type
assert_equal '{"name":"Name 1"}', @response.body
end
def test_render_array_using_explicit_serializer
get :render_array_using_explicit_serializer
assert_equal 'application/json', @response.content_type
expected = {
'paginated' => [
{ 'name' => 'Name 1' },
{ 'name' => 'Name 2' }
]
}
assert_equal expected.to_json, @response.body
end
def test_render_array_using_explicit_serializer
get :render_array_using_implicit_serializer
assert_equal 'application/json', @response.content_type
expected = [
{ 'name' => 'Name 1' },
{ 'name' => 'Name 2' }
]
assert_equal expected.to_json, @response.body
end
end
end
end

View File

@ -5,10 +5,18 @@ module ActionController
class JsonApiLinkedTest < ActionController::TestCase class JsonApiLinkedTest < ActionController::TestCase
class MyController < ActionController::Base class MyController < ActionController::Base
def setup_post 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 = Author.new(id: 1, name: 'Steve K.')
@author.posts = [] @author.posts = []
@author.bio = nil
@author.roles = [@role1, @role2]
@role1.author = @author
@role2.author = @author
@author2 = Author.new(id: 2, name: 'Anonymous') @author2 = Author.new(id: 2, name: 'Anonymous')
@author2.posts = [] @author2.posts = []
@author2.bio = nil
@author2.roles = []
@post = Post.new(id: 1, title: 'New Post', body: 'Body') @post = Post.new(id: 1, title: 'New Post', body: 'Body')
@first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
@second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT')
@ -20,47 +28,34 @@ module ActionController
@second_comment.author = nil @second_comment.author = nil
end end
def with_json_api_adapter
old_adapter = ActiveModel::Serializer.config.adapter
ActiveModel::Serializer.config.adapter = :json_api
yield
ensure
ActiveModel::Serializer.config.adapter = old_adapter
end
def render_resource_without_include def render_resource_without_include
with_json_api_adapter do setup_post
setup_post render json: @post, adapter: :json_api
render json: @post
end
end end
def render_resource_with_include def render_resource_with_include
with_json_api_adapter do setup_post
setup_post render json: @post, include: 'author', adapter: :json_api
render json: @post, include: 'author'
end
end end
def render_resource_with_nested_include def render_resource_with_nested_include
with_json_api_adapter do setup_post
setup_post render json: @post, include: 'comments.author', adapter: :json_api
render json: @post, include: 'comments.author' end
end
def render_resource_with_nested_has_many_include
setup_post
render json: @post, include: 'author,author.roles', adapter: :json_api
end end
def render_collection_without_include def render_collection_without_include
with_json_api_adapter do setup_post
setup_post render json: [@post], adapter: :json_api
render json: [@post]
end
end end
def render_collection_with_include def render_collection_with_include
with_json_api_adapter do setup_post
setup_post render json: [@post], include: 'author,comments', adapter: :json_api
render json: [@post], include: 'author,comments'
end
end end
end end
@ -80,6 +75,36 @@ module ActionController
assert_equal 'Steve K.', response['linked']['authors'].first['name'] assert_equal 'Steve K.', response['linked']['authors'].first['name']
end 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.",
"links" => {
"posts" => [],
"roles" => ["1", "2"],
"bio" => nil
}
}],
"roles"=>[{
"id" => "1",
"name" => "admin",
"links" => {
"author" => "1"
}
}, {
"id" => "2",
"name" => "colab",
"links" => {
"author" => "1"
}
}]
}
assert_equal expected_linked, response['linked']
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)

View File

@ -25,13 +25,9 @@ module ActionController
end end
def render_using_custom_root_in_adapter_with_a_default def render_using_custom_root_in_adapter_with_a_default
old_adapter = ActiveModel::Serializer.config.adapter
# 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, root: "profile" render json: @profile, root: "profile", adapter: :json_api
ensure
ActiveModel::Serializer.config.adapter = old_adapter
end end
def render_array_using_implicit_serializer def render_array_using_implicit_serializer

View File

@ -7,6 +7,8 @@ module ActiveModel
class BelongsToTest < Minitest::Test class BelongsToTest < Minitest::Test
def setup def setup
@author = Author.new(id: 1, name: 'Steve K.') @author = Author.new(id: 1, name: 'Steve K.')
@author.bio = nil
@author.roles = []
@post = Post.new(id: 42, title: 'New Post', body: 'Body') @post = Post.new(id: 42, title: 'New Post', body: 'Body')
@anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!')
@comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
@ -31,12 +33,28 @@ module ActiveModel
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')
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 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]})
assert_equal([{title: 'New Post'}], @adapter.serializable_hash[:linked][:posts]) expected = [{
title: 'New Post',
links: {
comments: ["1"],
author: "1"
}
}]
assert_equal expected, @adapter.serializable_hash[:linked][:posts]
end end
def test_include_nil_author def test_include_nil_author
@ -49,7 +67,53 @@ module ActiveModel
def test_include_type_for_association_when_is_different_than_name def test_include_type_for_association_when_is_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)
assert_equal({type: "author", id: "1"}, adapter.serializable_hash[:blogs][:links][:writer]) links = adapter.serializable_hash[:blogs][:links]
expected = {
writer: {
type: "author",
id: "1"
},
articles: {
type: "posts",
ids: ["42", "43"]
}
}
assert_equal expected, links
end
def test_include_linked_resources_with_type_name
serializer = BlogSerializer.new(@blog)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer, include: "writer,articles")
linked = adapter.serializable_hash[:linked]
expected = {
authors: [{
id: "1",
name: "Steve K.",
links: {
posts: [],
roles: [],
bio: nil
}
}],
posts: [{
title: "New Post",
body: "Body",
id: "42",
links: {
comments: ["1"],
author: "1"
}
}, {
title: "Hello!!",
body: "Hello, world!!",
id: "43",
links: {
comments: [],
author: nil
}
}]
}
assert_equal expected, linked
end end
end end
end end

View File

@ -7,6 +7,7 @@ module ActiveModel
class CollectionTest < Minitest::Test class CollectionTest < Minitest::Test
def setup def setup
@author = Author.new(id: 1, name: 'Steve K.') @author = Author.new(id: 1, name: 'Steve K.')
@author.bio = nil
@first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!')
@second_post = Post.new(id: 2, title: 'New Post', body: 'Body') @second_post = Post.new(id: 2, title: 'New Post', body: 'Body')
@first_post.comments = [] @first_post.comments = []

View File

@ -7,6 +7,8 @@ module ActiveModel
class HasManyEmbedIdsTest < Minitest::Test class HasManyEmbedIdsTest < Minitest::Test
def setup def setup
@author = Author.new(name: 'Steve K.') @author = Author.new(name: 'Steve K.')
@author.bio = nil
@author.roles = nil
@first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!')
@second_post = Post.new(id: 2, title: 'New Post', body: 'Body') @second_post = Post.new(id: 2, title: 'New Post', body: 'Body')
@author.posts = [@first_post, @second_post] @author.posts = [@first_post, @second_post]

View File

@ -8,6 +8,7 @@ module ActiveModel
def setup def setup
@author = Author.new(id: 1, name: 'Steve K.') @author = Author.new(id: 1, name: 'Steve K.')
@author.posts = [] @author.posts = []
@author.bio = nil
@post = Post.new(id: 1, title: 'New Post', body: 'Body') @post = Post.new(id: 1, title: 'New Post', body: 'Body')
@post_without_comments = Post.new(id: 2, title: 'Second Post', body: 'Second') @post_without_comments = Post.new(id: 2, title: 'Second Post', body: 'Second')
@first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
@ -34,18 +35,40 @@ module ActiveModel
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')
assert_equal([ expected = [{
{id: "1", body: 'ZOMG A COMMENT'}, id: "1",
{id: "2", body: 'ZOMG ANOTHER COMMENT'} body: 'ZOMG A COMMENT',
], @adapter.serializable_hash[:linked][:comments]) 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 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: [:body]}) @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'comments', fields: {comment: [:id]})
assert_equal([ expected = [{
{body: 'ZOMG A COMMENT'}, id: "1",
{body: 'ZOMG ANOTHER COMMENT'} links: {
], @adapter.serializable_hash[:linked][:comments]) post: "1",
author: nil
}
}, {
id: "2",
links: {
post: "1",
author: nil
}
}]
assert_equal expected, @adapter.serializable_hash[:linked][:comments]
end end
def test_no_include_linked_if_comments_is_empty def test_no_include_linked_if_comments_is_empty

View File

@ -7,6 +7,7 @@ module ActiveModel
class LinkedTest < Minitest::Test class LinkedTest < Minitest::Test
def setup def setup
@author = Author.new(id: 1, name: 'Steve K.') @author = Author.new(id: 1, name: 'Steve K.')
@bio = Bio.new(id: 1, content: 'AMS Contributor')
@first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!')
@second_post = Post.new(id: 2, title: 'New Post', body: 'Body') @second_post = Post.new(id: 2, title: 'New Post', body: 'Body')
@first_post.comments = [] @first_post.comments = []
@ -14,9 +15,12 @@ module ActiveModel
@first_post.author = @author @first_post.author = @author
@second_post.author = @author @second_post.author = @author
@author.posts = [@first_post, @second_post] @author.posts = [@first_post, @second_post]
@author.bio = @bio
@author.roles = []
@bio.author = @author
@serializer = ArraySerializer.new([@first_post, @second_post]) @serializer = ArraySerializer.new([@first_post, @second_post])
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'author,comments') @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'author,author.bio,comments')
end end
def test_include_multiple_posts_and_linked def test_include_multiple_posts_and_linked
@ -31,7 +35,42 @@ module ActiveModel
{ title: "Hello!!", body: "Hello, world!!", id: "1", links: { comments: ['1', '2'], author: "1" } }, { title: "Hello!!", body: "Hello, world!!", id: "1", links: { comments: ['1', '2'], author: "1" } },
{ title: "New Post", body: "Body", id: "2", links: { comments: [], :author => "1" } } { title: "New Post", body: "Body", id: "2", links: { comments: [], :author => "1" } }
], @adapter.serializable_hash[:posts]) ], @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." }] }, @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 end
end end

View File

@ -18,6 +18,26 @@ module ActiveModel
def test_serializer def test_serializer
assert_equal @serializer, @adapter.serializer assert_equal @serializer, @adapter.serializer
end end
def test_adapter_class_for_known_adapter
klass = ActiveModel::Serializer::Adapter.adapter_class(:json_api)
assert_equal ActiveModel::Serializer::Adapter::JsonApi, klass
end
def test_adapter_class_for_unknown_adapter
klass = ActiveModel::Serializer::Adapter.adapter_class(:json_simple)
assert_nil klass
end
def test_create_adapter
adapter = ActiveModel::Serializer::Adapter.create(@serializer)
assert_equal ActiveModel::Serializer::Adapter::Json, adapter.class
end
def test_create_adapter_with_override
adapter = ActiveModel::Serializer::Adapter.create(@serializer, { adapter: :json_api})
assert_equal ActiveModel::Serializer::Adapter::JsonApi, adapter.class
end
end end
end end
end end

33
test/fixtures/poro.rb vendored
View File

@ -35,10 +35,18 @@ class ProfileSerializer < ActiveModel::Serializer
urls :posts, :comments urls :posts, :comments
end end
class ProfilePreviewSerializer < ActiveModel::Serializer
attributes :name
urls :posts, :comments
end
Post = Class.new(Model) Post = Class.new(Model)
Comment = Class.new(Model) Comment = Class.new(Model)
Author = Class.new(Model) Author = Class.new(Model)
Bio = Class.new(Model)
Blog = Class.new(Model) Blog = Class.new(Model)
Role = Class.new(Model)
PostSerializer = Class.new(ActiveModel::Serializer) do PostSerializer = Class.new(ActiveModel::Serializer) do
attributes :title, :body, :id attributes :title, :body, :id
@ -59,6 +67,20 @@ AuthorSerializer = Class.new(ActiveModel::Serializer) do
attributes :id, :name attributes :id, :name
has_many :posts, embed: :ids 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
belongs_to :author
end end
BlogSerializer = Class.new(ActiveModel::Serializer) do BlogSerializer = Class.new(ActiveModel::Serializer) do
@ -67,3 +89,14 @@ BlogSerializer = Class.new(ActiveModel::Serializer) do
belongs_to :writer belongs_to :writer
has_many :articles has_many :articles
end end
PaginatedSerializer = Class.new(ActiveModel::Serializer::ArraySerializer) do
def json_key
'paginated'
end
end
AlternateBlogSerializer = Class.new(ActiveModel::Serializer) do
attribute :id
attribute :name, key: :title
end

View File

@ -26,6 +26,8 @@ module ActiveModel
def setup def setup
@author = Author.new(name: 'Steve K.') @author = Author.new(name: 'Steve K.')
@author.bio = nil
@author.roles = []
@post = Post.new({ title: 'New Post', body: 'Body' }) @post = Post.new({ title: 'New Post', body: 'Body' })
@comment = Comment.new({ id: 1, body: 'ZOMG A COMMENT' }) @comment = Comment.new({ id: 1, body: 'ZOMG A COMMENT' })
@post.comments = [@comment] @post.comments = [@comment]
@ -39,11 +41,25 @@ module ActiveModel
end end
def test_has_many def test_has_many
assert_equal({posts: {type: :has_many, options: {embed: :ids}}}, @author_serializer.class._associations) 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
)
@author_serializer.each_association do |name, serializer, options| @author_serializer.each_association do |name, serializer, options|
assert_equal(:posts, name) if name == :posts
assert_equal({embed: :ids}, options) assert_equal({embed: :ids}, options)
assert_kind_of(ActiveModel::Serializer.config.array_serializer, serializer) assert_kind_of(ActiveModel::Serializer.config.array_serializer, serializer)
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
end end
end end

View File

@ -0,0 +1,23 @@
require 'test_helper'
module ActiveModel
class Serializer
class AttributeTest < Minitest::Test
def setup
@blog = Blog.new({ id: 1, name: 'AMS Hints' })
@blog_serializer = AlternateBlogSerializer.new(@blog)
end
def test_attributes_definition
assert_equal([:id, :title],
@blog_serializer.class._attributes)
end
def test_json_serializable_hash
adapter = ActiveModel::Serializer::Adapter::Json.new(@blog_serializer)
assert_equal({:id=>1, :title=>"AMS Hints"}, adapter.serializable_hash)
end
end
end
end

View File

@ -7,7 +7,7 @@ module ActiveModel
assert_equal ActiveModel::Serializer::ArraySerializer, ActiveModel::Serializer.config.array_serializer assert_equal ActiveModel::Serializer::ArraySerializer, ActiveModel::Serializer.config.array_serializer
end end
def test_adapter def test_default_adapter
assert_equal :json, ActiveModel::Serializer.config.adapter assert_equal :json, ActiveModel::Serializer.config.adapter
end end
end end