mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-22 22:06:50 +00:00
Include 'linked' member for json-api collections
The options passed to the render are partitioned into adapter options and serializer options. 'include' and 'root' are sent to the adapter, not sure what options would go directly to serializer, but leaving this in until I understand that better.
This commit is contained in:
parent
80ece39dd9
commit
d5bae0c2f0
2
.gitignore
vendored
2
.gitignore
vendored
@ -16,4 +16,4 @@ test/tmp
|
||||
test/version_tmp
|
||||
tmp
|
||||
*.swp
|
||||
.ruby-version
|
||||
.ruby-version
|
||||
|
||||
67
README.md
67
README.md
@ -1,8 +1,8 @@
|
||||
# ActiveModel::Serializers
|
||||
|
||||
# ActiveModel::Serializers
|
||||
|
||||
[](https://travis-ci.org/rails-api/active_model_serializers)
|
||||
|
||||
ActiveModel::Serializers brings convention over configuration to your JSON generation.
|
||||
ActiveModel::Serializers brings convention over configuration to your JSON generation.
|
||||
|
||||
AMS does this through two components: **serializers** and **adapters**. Serializers describe which attributes and relationships should be serialized. Adapters describe how attributes and relationships should be serialized.
|
||||
|
||||
@ -32,7 +32,7 @@ serializers:
|
||||
```ruby
|
||||
class PostSerializer < ActiveModel::Serializer
|
||||
attributes :title, :body
|
||||
|
||||
|
||||
has_many :comments
|
||||
|
||||
url :post
|
||||
@ -61,7 +61,7 @@ ActiveModel::Serializer.config.adapter = ActiveModel::Serializer::Adapter::HalAd
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
|
||||
```ruby
|
||||
ActiveModel::Serializer.config.adapter = :hal
|
||||
```
|
||||
@ -85,18 +85,27 @@ end
|
||||
In this case, Rails will look for a serializer named `PostSerializer`, and if
|
||||
it exists, use it to serialize the `Post`.
|
||||
|
||||
## Installation
|
||||
|
||||
Add this line to your application's Gemfile:
|
||||
### Built in Adapters
|
||||
|
||||
```
|
||||
gem 'active_model_serializers'
|
||||
The `:json_api` adapter will include the associated resources in the `"linked"`
|
||||
member when the resource names are included in the `include` option.
|
||||
|
||||
```ruby
|
||||
render @posts, include: 'authors,comments'
|
||||
```
|
||||
|
||||
And then execute:
|
||||
|
||||
```
|
||||
$ bundle
|
||||
## Installation
|
||||
|
||||
Add this line to your application's Gemfile:
|
||||
|
||||
```
|
||||
gem 'active_model_serializers'
|
||||
```
|
||||
|
||||
And then execute:
|
||||
|
||||
```
|
||||
$ bundle
|
||||
```
|
||||
|
||||
## Creating a Serializer
|
||||
@ -141,29 +150,27 @@ class CommentSerializer < ActiveModel::Serializer
|
||||
end
|
||||
```
|
||||
|
||||
The attribute names are a **whitelist** of attributes to be serialized.
|
||||
|
||||
The attribute names are a **whitelist** of attributes to be serialized.
|
||||
|
||||
The `has_many` and `belongs_to` declarations describe relationships between
|
||||
resources. By default, when you serialize a `Post`, you will
|
||||
get its `Comment`s as well.
|
||||
resources. By default, when you serialize a `Post`, you will get its `Comment`s
|
||||
as well.
|
||||
|
||||
The `url` declaration describes which named routes to use while generating URLs
|
||||
for your JSON. Not every adapter will require URLs.
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you find a bug, please report an
|
||||
[Issue](https://github.com/rails-api/active_model_serializers/issues/new).
|
||||
If you find a bug, please report an [Issue](https://github.com/rails-api/active_model_serializers/issues/new).
|
||||
|
||||
If you have a question, please [post to Stack
|
||||
Overflow](http://stackoverflow.com/questions/tagged/active-model-serializers).
|
||||
If you have a question, please [post to Stack Overflow](http://stackoverflow.com/questions/tagged/active-model-serializers).
|
||||
|
||||
Thanks!
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork it ( https://github.com/rails-api/active_model_serializers/fork )
|
||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||
3. Commit your changes (`git commit -am 'Add some feature'`)
|
||||
4. Push to the branch (`git push origin my-new-feature`)
|
||||
5. Create a new Pull Request
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork it ( https://github.com/rails-api/active_model_serializers/fork )
|
||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||
3. Commit your changes (`git commit -am 'Add some feature'`)
|
||||
4. Push to the branch (`git push origin my-new-feature`)
|
||||
5. Create a new Pull Request
|
||||
|
||||
@ -6,15 +6,18 @@ module ActionController
|
||||
|
||||
include ActionController::Renderers
|
||||
|
||||
ADAPTER_OPTION_KEYS = [:include, :root]
|
||||
|
||||
[:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
|
||||
define_method renderer_method do |resource, options|
|
||||
serializer = ActiveModel::Serializer.serializer_for(resource)
|
||||
|
||||
if serializer
|
||||
adapter_opts, serializer_opts =
|
||||
options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }
|
||||
# omg hax
|
||||
object = serializer.new(resource, options)
|
||||
adapter = ActiveModel::Serializer.adapter.new(object)
|
||||
|
||||
object = serializer.new(resource, Hash[serializer_opts])
|
||||
adapter = ActiveModel::Serializer.adapter.new(object, Hash[adapter_opts])
|
||||
super(adapter, options)
|
||||
else
|
||||
super(resource, options)
|
||||
@ -23,4 +26,3 @@ module ActionController
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ module ActiveModel
|
||||
|
||||
def initialize(serializer, options = {})
|
||||
@serializer = serializer
|
||||
@options = options
|
||||
end
|
||||
|
||||
def serializable_hash(options = {})
|
||||
|
||||
@ -5,16 +5,19 @@ module ActiveModel
|
||||
def initialize(serializer, options = {})
|
||||
super
|
||||
serializer.root = true
|
||||
@hash = {}
|
||||
@top = @options.fetch(:top) { @hash }
|
||||
end
|
||||
|
||||
def serializable_hash(options = {})
|
||||
@root = (options[:root] || serializer.json_key.to_s.pluralize).to_sym
|
||||
@hash = {}
|
||||
@root = (@options[:root] || serializer.json_key.to_s.pluralize).to_sym
|
||||
|
||||
if serializer.respond_to?(:each)
|
||||
@hash[@root] = serializer.map{|s| self.class.new(s).serializable_hash[@root] }
|
||||
@hash[@root] = serializer.map do |s|
|
||||
self.class.new(s, @options.merge(top: @top)).serializable_hash[@root]
|
||||
end
|
||||
else
|
||||
@hash[@root] = attributes_for_serializer(serializer, {})
|
||||
@hash[@root] = attributes_for_serializer(serializer, @options)
|
||||
|
||||
serializer.each_association do |name, association, opts|
|
||||
@hash[@root][:links] ||= {}
|
||||
@ -44,10 +47,10 @@ module ActiveModel
|
||||
@hash[@root][:links][name][:ids] += serializers.map{|serializer| serializer.id.to_s }
|
||||
end
|
||||
|
||||
unless options[:embed] == :ids || serializers.count == 0
|
||||
@hash[:linked] ||= {}
|
||||
@hash[:linked][name] ||= []
|
||||
@hash[:linked][name] += serializers.map { |item| attributes_for_serializer(item, options) }
|
||||
unless serializers.none? || @options[:embed] == :ids
|
||||
serializers.each do |serializer|
|
||||
add_linked(name, serializer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -62,17 +65,31 @@ module ActiveModel
|
||||
@hash[@root][:links][name][:id] = serializer.id.to_s
|
||||
end
|
||||
|
||||
unless options[:embed] == :ids
|
||||
plural_name = name.to_s.pluralize.to_sym
|
||||
@hash[:linked] ||= {}
|
||||
@hash[:linked][plural_name] ||= []
|
||||
@hash[:linked][plural_name].push attributes_for_serializer(serializer, options)
|
||||
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
|
||||
|
||||
def attributes_for_serializer(serializer, options)
|
||||
@ -80,6 +97,10 @@ module ActiveModel
|
||||
attributes[:id] = attributes[:id].to_s if attributes[:id]
|
||||
attributes
|
||||
end
|
||||
|
||||
def include_assoc? assoc
|
||||
@options[:include] && @options[:include].split(',').include?(assoc.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
104
test/action_controller/json_api_linked_test.rb
Normal file
104
test/action_controller/json_api_linked_test.rb
Normal file
@ -0,0 +1,104 @@
|
||||
require 'test_helper'
|
||||
|
||||
module ActionController
|
||||
module Serialization
|
||||
class JsonApiLinkedTest < ActionController::TestCase
|
||||
class MyController < ActionController::Base
|
||||
def setup_post
|
||||
@author = Author.new(id: 1, name: 'Steve K.')
|
||||
@author.posts = []
|
||||
@author2 = Author.new(id: 2, name: 'Anonymous')
|
||||
@author2.posts = []
|
||||
@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')
|
||||
@post.comments = [@first_comment, @second_comment]
|
||||
@post.author = @author
|
||||
@first_comment.post = @post
|
||||
@first_comment.author = @author2
|
||||
@second_comment.post = @post
|
||||
@second_comment.author = nil
|
||||
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
|
||||
with_json_api_adapter do
|
||||
setup_post
|
||||
render json: @post
|
||||
end
|
||||
end
|
||||
|
||||
def render_resource_with_include
|
||||
with_json_api_adapter do
|
||||
setup_post
|
||||
render json: @post, include: 'author'
|
||||
end
|
||||
end
|
||||
|
||||
def render_resource_with_nested_include
|
||||
with_json_api_adapter do
|
||||
setup_post
|
||||
render json: @post, include: 'comments.author'
|
||||
end
|
||||
end
|
||||
|
||||
def render_collection_without_include
|
||||
with_json_api_adapter do
|
||||
setup_post
|
||||
render json: [@post]
|
||||
end
|
||||
end
|
||||
|
||||
def render_collection_with_include
|
||||
with_json_api_adapter do
|
||||
setup_post
|
||||
render json: [@post], include: 'author,comments'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
tests MyController
|
||||
|
||||
def test_render_resource_without_include
|
||||
get :render_resource_without_include
|
||||
response = JSON.parse(@response.body)
|
||||
refute response.key? 'linked'
|
||||
end
|
||||
|
||||
def test_render_resource_with_include
|
||||
get :render_resource_with_include
|
||||
response = JSON.parse(@response.body)
|
||||
assert response.key? 'linked'
|
||||
assert_equal 1, response['linked']['authors'].size
|
||||
assert_equal 'Steve K.', response['linked']['authors'].first['name']
|
||||
end
|
||||
|
||||
def test_render_resource_with_nested_include
|
||||
get :render_resource_with_nested_include
|
||||
response = JSON.parse(@response.body)
|
||||
assert response.key? 'linked'
|
||||
assert_equal 1, response['linked']['authors'].size
|
||||
assert_equal 'Anonymous', response['linked']['authors'].first['name']
|
||||
end
|
||||
|
||||
def test_render_collection_without_include
|
||||
get :render_collection_without_include
|
||||
response = JSON.parse(@response.body)
|
||||
refute response.key? 'linked'
|
||||
end
|
||||
|
||||
def test_render_collection_with_include
|
||||
get :render_collection_with_include
|
||||
response = JSON.parse(@response.body)
|
||||
assert response.key? 'linked'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -11,8 +11,9 @@ module ActiveModel
|
||||
@comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
|
||||
@post.comments = [@comment]
|
||||
@anonymous_post.comments = []
|
||||
@comment.post = @post
|
||||
@post.author = @author
|
||||
@comment.post = @post
|
||||
@comment.author = nil
|
||||
@anonymous_post.author = nil
|
||||
|
||||
@serializer = CommentSerializer.new(@comment)
|
||||
|
||||
@ -13,11 +13,13 @@ module ActiveModel
|
||||
@post.comments = [@comment]
|
||||
@anonymous_post.comments = []
|
||||
@comment.post = @post
|
||||
@comment.author = nil
|
||||
@post.author = @author
|
||||
@anonymous_post.author = nil
|
||||
@blog = Blog.new(id: 1, name: "My Blog!!")
|
||||
@blog.writer = @author
|
||||
@blog.articles = [@post, @anonymous_post]
|
||||
@author.posts = []
|
||||
|
||||
@serializer = CommentSerializer.new(@comment)
|
||||
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer)
|
||||
@ -28,6 +30,7 @@ module ActiveModel
|
||||
end
|
||||
|
||||
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])
|
||||
end
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ module ActiveModel
|
||||
class Serializer
|
||||
class Adapter
|
||||
class JsonApi
|
||||
class Collection < Minitest::Test
|
||||
class CollectionTest < Minitest::Test
|
||||
def setup
|
||||
@author = Author.new(id: 1, name: 'Steve K.')
|
||||
@first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!')
|
||||
@ -13,6 +13,7 @@ module ActiveModel
|
||||
@second_post.comments = []
|
||||
@first_post.author = @author
|
||||
@second_post.author = @author
|
||||
@author.posts = [@first_post, @second_post]
|
||||
|
||||
@serializer = ArraySerializer.new([@first_post, @second_post])
|
||||
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer)
|
||||
@ -20,8 +21,8 @@ module ActiveModel
|
||||
|
||||
def test_include_multiple_posts
|
||||
assert_equal([
|
||||
{title: "Hello!!", body: "Hello, world!!", id: "1", links: {comments: [], author: "1"}},
|
||||
{title: "New Post", body: "Body", id: "2", links: {comments: [], author: "1"}}
|
||||
{ title: "Hello!!", body: "Hello, world!!", id: "1", links: { comments: [], author: "1" } },
|
||||
{ title: "New Post", body: "Body", id: "2", links: { comments: [], author: "1" } }
|
||||
], @adapter.serializable_hash[:posts])
|
||||
end
|
||||
end
|
||||
|
||||
@ -12,6 +12,8 @@ module ActiveModel
|
||||
@author.posts = [@first_post, @second_post]
|
||||
@first_post.author = @author
|
||||
@second_post.author = @author
|
||||
@first_post.comments = []
|
||||
@second_post.comments = []
|
||||
|
||||
@serializer = AuthorSerializer.new(@author)
|
||||
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer)
|
||||
|
||||
@ -7,10 +7,13 @@ module ActiveModel
|
||||
class HasManyTest < Minitest::Test
|
||||
def setup
|
||||
@author = Author.new(id: 1, name: 'Steve K.')
|
||||
@author.posts = []
|
||||
@post = Post.new(id: 1, title: 'New Post', body: 'Body')
|
||||
@post_without_comments = Post.new(id: 2, title: 'Second Post', body: 'Second')
|
||||
@first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
|
||||
@first_comment.author = nil
|
||||
@second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT')
|
||||
@second_comment.author = nil
|
||||
@post.comments = [@first_comment, @second_comment]
|
||||
@post_without_comments.comments = []
|
||||
@first_comment.post = @post
|
||||
@ -30,6 +33,7 @@ module ActiveModel
|
||||
end
|
||||
|
||||
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'}
|
||||
|
||||
40
test/adapter/json_api/linked_test.rb
Normal file
40
test/adapter/json_api/linked_test.rb
Normal file
@ -0,0 +1,40 @@
|
||||
require 'test_helper'
|
||||
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
class Adapter
|
||||
class JsonApi
|
||||
class LinkedTest < Minitest::Test
|
||||
def setup
|
||||
@author = Author.new(id: 1, name: 'Steve K.')
|
||||
@first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!')
|
||||
@second_post = Post.new(id: 2, title: 'New Post', body: 'Body')
|
||||
@first_post.comments = []
|
||||
@second_post.comments = []
|
||||
@first_post.author = @author
|
||||
@second_post.author = @author
|
||||
@author.posts = [@first_post, @second_post]
|
||||
|
||||
@serializer = ArraySerializer.new([@first_post, @second_post])
|
||||
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'author,comments')
|
||||
end
|
||||
|
||||
def test_include_multiple_posts_and_linked
|
||||
@first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
|
||||
@second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT')
|
||||
@first_post.comments = [@first_comment, @second_comment]
|
||||
@first_comment.post = @first_post
|
||||
@first_comment.author = nil
|
||||
@second_comment.post = @first_post
|
||||
@second_comment.author = nil
|
||||
assert_equal([
|
||||
{ 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." }] }, @adapter.serializable_hash[:linked])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
1
test/fixtures/poro.rb
vendored
1
test/fixtures/poro.rb
vendored
@ -52,6 +52,7 @@ CommentSerializer = Class.new(ActiveModel::Serializer) do
|
||||
attributes :id, :body
|
||||
|
||||
belongs_to :post
|
||||
belongs_to :author
|
||||
end
|
||||
|
||||
AuthorSerializer = Class.new(ActiveModel::Serializer) do
|
||||
|
||||
@ -30,6 +30,7 @@ module ActiveModel
|
||||
@comment = Comment.new({ id: 1, body: 'ZOMG A COMMENT' })
|
||||
@post.comments = [@comment]
|
||||
@comment.post = @post
|
||||
@comment.author = nil
|
||||
@post.author = @author
|
||||
@author.posts = [@post]
|
||||
|
||||
@ -47,11 +48,17 @@ module ActiveModel
|
||||
end
|
||||
|
||||
def test_has_one
|
||||
assert_equal({post: {type: :belongs_to, options: {}}}, @comment_serializer.class._associations)
|
||||
assert_equal({post: {type: :belongs_to, options: {}}, :author=>{:type=>:belongs_to, :options=>{}}}, @comment_serializer.class._associations)
|
||||
@comment_serializer.each_association do |name, serializer, options|
|
||||
assert_equal(:post, name)
|
||||
assert_equal({}, options)
|
||||
assert_kind_of(PostSerializer, serializer)
|
||||
if name == :post
|
||||
assert_equal({}, options)
|
||||
assert_kind_of(PostSerializer, serializer)
|
||||
elsif name == :author
|
||||
assert_equal({}, options)
|
||||
assert_nil serializer
|
||||
else
|
||||
flunk "Unknown association: #{name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Loading…
Reference in New Issue
Block a user