I am using AMS version 0.10 and looking to use the json-api specification for rendering my responses. However, I am having difficultly rendering the 'included' key for my relationship data. I have the following setup:
products_controller.rb
class Api::V1::ProductsController < ApplicationController
...
respond_to :json
def show
respond_with Product.find(params[:id])
end
...
product_serializer.rb
class ProductSerializer < ActiveModel::Serializer
attributes :id, :title, :price, :published
has_one :user
end
user_serializer.rb
class UserSerializer < ActiveModel::Serializer
attributes :id, :email, :auth_token, :created_at, :updated_at
end
products_controller_spec.rb
before(:each) do
#product = FactoryGirl.create :product
get :show, params: { id: #product.id }
end
...
it "has the user as a embeded object" do
product_response = json_response
puts "&&&&&&&&&&&&&&&"
puts product_response #output below
#expect(product_response[:user][:email]).to eql #product.user.email
end
...
json_response
{:data=>{:id=>"1", :type=>"products", :attributes=>{..working..}, :relationships=>{:user=>{:data=>{:id=>"1", :type=>"users"}}}}}
I would like to know how to get the 'included' section for the nested resource.
Example (from http://jsonapi.org/format/#introduction)
{
"data": [{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON API paints my bikeshed!"
},
"links": {
"self": "http://example.com/articles/1"
},
"relationships": {
"author": {
"links": {
"self": "http://example.com/articles/1/relationships/author",
"related": "http://example.com/articles/1/author"
},
"data": { "type": "people", "id": "9" }
}
}],
"included": [{
"type": "people",
"id": "9",
"attributes": {
"first-name": "Dan",
"last-name": "Gebhardt",
"twitter": "dgeb"
},
"links": {
"self": "http://example.com/people/9"
}
},
I have never used AMS before so any help will be greatly appreciated.
Many thanks
Just for anyone else the solution is here https://github.com/rails-api/active_model_serializers/blob/master/docs/jsonapi/schema.md.
Essentially i add the following to my controller action (GET products/1)
render json: product, include: params[:include]
This will allow the requesting system to determine whether they would like to include the nested models by adding the parameter include='user' for the api to process.
Thanks
Related
I'm using Netflix's jsonapi-rails gem to serialize my API. I need to build a response.json object that includes the associated comments for a post.
Post model:
class Post < ApplicationRecord
has_many :comments, as: :commentable
end
Polymorphic Comment model
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
PostSerializer
class PostSerializer
include FastJsonapi::ObjectSerializer
attributes :body
has_many :comments, serializer: CommentSerializer, polymorphic: true
end
CommentSerializer
class CommentSerializer
include FastJsonapi::ObjectSerializer
attributes :body, :id, :created_at
belongs_to :post
end
Posts#index
class PostsController < ApplicationController
def index
#posts = Post.all
hash = PostSerializer.new(#posts).serialized_json
render json: hash
end
end
What I've got so far only gives me the comment type and id, but I NEED the comment's body as well.
Please help!
Thanks in advance~!
Although not very intuitive this behaviour is there by design. According to the JSON API relationship data and actual related resource data belong in different objects of the structure
You can read more here:
Fetching Relationships
Inclusion of Related Resources
To include the body of the Comments your serializers would have to be:
class PostSerializer
include FastJsonapi::ObjectSerializer
attributes :body, :created_at
has_many :comments
end
class CommentSerializer
include FastJsonapi::ObjectSerializer
attributes :body, :created_at
end
and your controller code:
class HomeController < ApplicationController
def index
#posts = Post.all
options = {include: [:comments]}
hash = PostSerializer.new(#posts, options).serialized_json
render json: hash
end
end
The response for a single Post would look something like this:
{
"data": [
{
"attributes": {
"body": "A test post!"
},
"id": "1",
"relationships": {
"comments": {
"data": [
{
"id": "1",
"type": "comment"
},
{
"id": "2",
"type": "comment"
}
]
}
},
"type": "post"
}
],
"included": [
{
"attributes": {
"body": "This is a comment 1 body!",
"created_at": "2018-05-06 22:41:53 UTC"
},
"id": "1",
"type": "comment"
},
{
"attributes": {
"body": "This is a comment 2 body!",
"created_at": "2018-05-06 22:41:59 UTC"
},
"id": "2",
"type": "comment"
}
]
}
I'm working on an API based Rails application, I don't know how to show all associated items in one request like my models
todo.rb
class Todo < ApplicationRecord
has_many :items, dependent: :destroy
validates_presence_of :title, :created_by
end
item.rb
class Item < ApplicationRecord
belongs_to :todo
validates_presence_of :name
end
in the controller
def show
json_response(#todo)
end
private
def set_todo
#todo = Todo.find(params[:id])
end
The endpoit for single todo
https://example.com/todos/1
and it showing like this
{
"id": 1,
"title": "Hello World!",
"created_by": "2",
"created_at": "2018-04-26T11:19:31.433Z",
"updated_at": "2018-04-26T11:19:31.433Z"
}
My question is how do I show all items which created by this todo in same end point request.
Try using ActiveModel::Serializers::JSON#as_json
For e.g.
def show
render json: #user.as_json(include: :posts), status: 200
end
That should return a response like
{
"id": 1,
"name": "Konata Izumi",
"age": 16,
"created_at": "2006/08/01",
"awesome": true,
"posts": [
{
"id": 1,
"author_id": 1,
"title": "Welcome to the weblog"
},
{
"id": 2,
"author_id": 1,
"title": "So I was thinking"
}
]
}
def show
render json: {"todos" => {"todo" => #todo, "items" => #todo.items }}, status:200
end
Alternatively you can use serializers for more filters and modifications of response datas
I'm trying to write an update method that processes JSON. The JSON looks like this:
{
"organization": {
"id": 1,
"nodes": [
{
"id": 1,
"title": "Hello",
"description": "My description."
},
{
"id": 101,
"title": "fdhgh",
"description": "My description."
}
]
}
}
Organization model:
has_many :nodes
accepts_nested_attributes_for :nodes, reject_if: :new_record?
Organization serializer:
attributes :id
has_many :nodes
Node serializer:
attributes :id, :title, :description
Update method in the organizations controller:
def update
organization = Organization.find(params[:id])
if organization.update_attributes(nodes_attributes: node_params.except(:id))
render json: organization, status: :ok
else
render json: organization, status: :failed
end
end
private
def node_params
params.require(:organization).permit(nodes: [:id, :title, :description])
end
I also tried adding accepts_nested_attributes_for to the organization serializer, but that does not seem to be correct as it generated an error (undefined method 'accepts_nested_attributes_for'), so I've only added accepts_nested_attributes_for to the model and not to the serializer.
The code above generates the error below, referring to the update_attributes line in the update method. What am I doing wrong?
no implicit conversion of String into Integer
In debugger node_params returns:
Unpermitted parameters: id
{"nodes"=>[{"id"=>101, "title"=>"gsdgdsfgsdg.", "description"=>"dgdsfgd."}, {"id"=>1, "title"=>"ertret.", "description"=>"etewtete."}]}
Update: Got it to work using the following:
def update
organization = Organization.find(params[:id])
if organization.update_attributes(nodes_params)
render json: organization, status: :ok
else
render json: organization, status: :failed
end
end
private
def node_params
params.require(:organization).permit(:id, nodes_attributes: [:id, :title, :description])
end
To the serializer I added root: :nodes_attributes.
It now all works, but I'm concerned about including the id in node_params. Is that safe? Wouldn't it now be possible to edit the id of the organization and node (which shouldn't be allowed)? Would the following be a proper solution to not allowing it to update the id's:
if organization.update_attributes(nodes_params.except(:id, nodes_attributes: [:id]))
looks super close.
Your json child object 'nodes' need to be 'nodes_attributes'.
{
"organization": {
"id": 1,
"nodes_attributes": [
{
"id": 1,
"title": "Hello",
"description": "My description."
},
{
"id": 101,
"title": "fdhgh",
"description": "My description."
}
]
}
}
You can do this sort of thing. Put this in your controller.
before_action do
if params[:organization]
params[:organization][:nodes_attributes] ||= params[:organization].delete :nodes
end
end
It will set the correct attribute in params and still use all the accepts_nested_attributes features.
I am trying to use custom serializers for the relationships in a serializer and the json_api adapter enabled. However the relationships are not serialized correctly (or, better, not at all displayed/serialized).
PostController.rb
def index
render json: Post.all, each_serializer: Serializers::PostSerializer
end
Serializer
module Api
module V1
module Serializers
class PostSerializer < ActiveModel::Serializer
attributes :title, :id
belongs_to :author, serializer: UserSerializer
has_many :post_sections, serializer: PostSectionSerializer
end
end
end
end
JSON output:
{
"data": [
{
"attributes": {
"title": "Test Title"
},
"id": "1",
"relationships": {
"author": {
"data": {
"id": "1",
"type": "users"
}
},
"post_sections": {
"data": [
{
"id": "1",
"type": "post_sections"
}
]
}
},
"type": "posts"
}
]
}
As you can see, the relationships are not fulfilled, which happens only if I specify a custom serializer for the relationships!!
If I do something like this:
module Api
module V1
module Serializers
class PostSerializer < ActiveModel::Serializer
attributes :title, :id
belongs_to :author # no custom serializer!
has_many :post_sections # no custom serializer!
end
end
end
end
The relationships are shown correctly, but not using a custom serializer...
What's the issue here ?
EDIT
According to the json API 1.0 Format, what I am getting back is the so-called resource identifier object.
The following primary data is a single resource identifier object that
references the same resource:
{ "data": {
"type": "articles",
"id": "1" } }
Is there a way to prevent getting resource identifier objects, and get the actual data instead ?
Relationships only returns id and type according to json-api exmaples. If you need to return more information about this relation you should add include option on your render action.
Ex.
PostController.rb
class PostsController < ApplicationController
def show
render json: #post, include: 'comments'
end
end
Serializers
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :content
has_many :comment, serializer: CommentSerializer
end
class CommentSerializer < ActiveModel::Serializer
attributes :id, :title, :content
end
JSON output:
{
"data": {
"id": "1",
"type": "post",
"attributes": {
"title": "bla",
"content": "bla"
},
"relationships": {
"comment": {
"data": [
{
"type": "comments",
"id": "1"
}
]
}
}
},
"included": {
{
"id": "1",
"type": "comments",
"attributes": {
"title": "test",
"content": "test"
}
}
]
}
Just to add to #Bruno Bacarini's answer, you may also include chained associations by using:
render #posts, include: ['authors.profile', 'comments']
source: joaomdmoura's comment on github
I would like to refactor an existing rails API using active model serializers.
Unfortunately the existing API uses a slightly different JSON schema than any of the the existing adapters and I have been unable to recreate it using AMS.
The schema I am trying to recreate is like this:
{
"status": 0,
"message": "OK",
"timestamp": 1438940571,
"data": {
"contacts": [
{
"contact": {
"id": "1",
"first_name": "Kung Foo",
"last_name": "Panda",
"created_at": "2015-07-23T14:09:20.850Z",
"modified_at": "2015-08-04T15:21:36.639Z"
}
},
{
"contact": {
"id": "2",
"first_name": "Johnny",
"last_name": "Bravo",
"created_at": "2015-07-23T14:09:20.850Z",
"modified_at": "2015-08-04T15:21:36.639Z"
}
}
]
}
}
I am wondering is there a way to create a custom adapter for active model serializers, or otherwise create this schema.
Could just use a couple of serializers.
class MessageSerializer < ActiveModel::Serializer
attributes :status, :message, :timestamp, :data
# We could've just used that, were it not for nested hashes
# has_many :contacts, key: :data
attributes :data
def data
ActiveModel::ArraySerializer.new(object.contacts, root: 'contacts')
end
end
class ContactSerializer < ActiveModel::Serializer
attributes :id, :first_name, :last_name, :created_at, :modified_at
def root
'contact'
end
end
There seems to be no better way to do that
Then somewhere in your controller:
render json: Message.serializer.new(#message, root: false)