Say I have a model User and a serializer UserSerializer < ActiveModel::Serializer, and a controller that looks like this:
class UsersController < ApplicationController
respond_to :json
def index
respond_with User.all
end
end
Now if I visit /users I'll get a JSON response that looks like this:
{
"users": [
{
"id": 7,
"name": "George"
},
{
"id": 8,
"name": "Dave"
}
.
.
.
]
}
But what if I want to include some extra information in the JSON response that isn't relevant to any one particular User? E.g.:
{
"time": "2014-01-06 16:52 GMT",
"url": "http://www.example.com",
"noOfUsers": 2,
"users": [
{
"id": 7,
"name": "George"
},
{
"id": 8,
"name": "Dave"
}
.
.
.
]
}
This example is contrived but it's a good approximation of what I want to achieve. Is this possible with active model serializers? (Perhaps by subclassing ActiveModel::ArraySerializer? I couldn't figure it out). How do I add extra root elements?
You can pass them as the second arguement to respond_with
def index
respond_with User.all, meta: {time: "2014-01-06 16:52 GMT",url: "http://www.example.com", noOfUsers: 2}
end
In version 0.9.3 in an initializer set ActiveModel::Serializer.root = true:
ActiveSupport.on_load(:active_model_serializers) do
# Disable for all serializers (except ArraySerializer)
ActiveModel::Serializer.root = true
end
In controller
render json: #user, meta: { total: 10 }
Got it working using render:
render json: {
"time": "2014-01-06 16:52 GMT",
"url": "http://www.example.com",
"noOfUsers": 2,
"users": #users
}
The problem is, this doesn't call UserSerializer, it just calls .as_json on each individual user object and skips the Serializer. So I had to do it explicitly:
def index
.
.
.
render json: {
"time": "2014-01-06 16:52 GMT",
"url": "http://www.example.com",
"noOfUsers": 2,
"users": serialized_users
}
end
def serialized_users
ActiveModel::ArraySerializer.new(#users).as_json
end
Not the most elegant solution but it works.
Just a simple hack if you don't want to modify either the serializer or render:
data = serializer.new(object, root: false)
# cannot modify data here since it is a serializer class
data = data.as_json
# do whatever to data as a Hash and pass the result for render
data[:extra] = 'extra stuff'
render json: data
I was able to get this working for my use case by just adding the following in my controller. Nothing else needed with AMS 0.10.
render
json: #user,
meta: {
time: "2014-01-06 16:52 GMT",
url: "http://www.example.com",
noOfUsers: 2
}
Related
I want to use a serializer within another serializer so I can add a key-value pair at the top level but seems like when I do, the lower level serializer isn't working anymore-
My files:
ItemsController
class ItemsController
def index
open_items = Items.
select("distinct on (open_item_id) *").
preload(:company, :project)
total = open_items.count("id")
render json: {
total: total,
items: paginate(open_items, per_page: 2), serializer: ItemsSerializer
}, status: :ok
end
end
ItemsSerializer
class ItemsSerializer < ActiveModel::Serializer
attribute :total
has_many :items, serializer: ItemSerializer
end
ItemSerializer
class ItemSerializer < ActiveModel::Serializer
attributes :id,
:project,
:company,
def company
{
name: object.company.name,
id: object.company.id
}
end
def project
{
name: object.project.name,
id: object.project.id
}
end
end
I want to get another key/value pair to my serializer output in the below so that I can get something like this:
{
"total": 1,
"items": [
{
"id": 42920375,
"company": {
"id": 123,
"name": "CompanyName"
},
"project": {
"id": 456,
"name": "ProjectName"
}
}
]
}
But currently, I'm getting:
{
"total": 1,
"items": [
{
"id": 42920375,
"company_id": 5842,
"project_id": 191741,
}
]
}
I don't think you can use ItemsSerializer that way. It needs to correspond to a model.
Active-Model-serializer will automatically serialize each object in an association with its own serializer:
"In your controllers, when you use render :json for an array of objects, AMS will use ActiveModel::ArraySerializer (included in this project) as the base serializer, and the individual Serializer for the objects contained in that array."
So there's no need to reinvent the wheels. Just do this:
render json: paginate(open_items, per_page: 2), status: :ok
Then each item will be processed by ItemSerializer. I don't see a way to add total here though.
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
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 attempting to include some extra bits in my JSON using the below in my vehicles_controller:
# GET /vehicles/1
# GET /vehicles/1.json
def show
#vehicle = Vehicle.find(params[:id])
respond_to do |format|
format.json { #vehicle.to_json(:methods => [:product_applications_with_notes], :include => [:product_applications]) }
end
end
The vehicle model has both the method :product_applications_with_notes and the relationship has_many: :product_applications. However, when I run a request to http://localhost:3000/vehicles/1 the JSON output is as below:
{
"id": 1,
"make": "Acura",
"model": "ALL",
"year": 2001,
"body_style": "Car",
"created_at": "2014-10-22T20:06:00.157Z",
"updated_at": "2014-10-22T20:07:09.827Z"
}
It does not show the included extra bits. Why?
try to override the as_json method in Vehicle model.
something like:
def as_json(options=nil)
json_hash = super(options)
json_hash[:product_applications] = product_applications
json_hash
end
Context: ruby "2.0.0" and rails "4.0.0"
I am wondering how to return more data than a simple JSON representation of my model. I have this method in my API controller:
def show
#entry = params[:id].to_i != 0 ? Entry.find(params[:id]) : Entry.where(slug: params[:id]).first
respond_with #entry
end
which returns a well formatted JSON article when called via http://pow.url.dev/en/tenant/api/v1/entries/23.
I also have two other method to get the next and previous article, for instance, next looks like this:
def next
#current_entry = params[:entry_id].to_i != 0 ? Entry.find(params[:entry_id]) : Entry.where(slug: params[:entry_id]).first
#entry = Entry.where( status_id: 0 ).where( "published_at > ?", #current_entry.published_at ).first
respond_with #entry
end
The problem is that the client application needs to make 3 API calls to 1) fetch an article, 2) fetch the next article and 3) fetch the previous article.
It would be much better if the client had to do only one call to get all the information at once.
I would like to add into the show method the Title and ID of the next and previous article along with the JSON result, what would be the best way to do that?
Quick and dirty:
def show
#entry = ...
render :json => #entry.attributes.merge({
:next_id => id_of_next_article,
:next_title => title_of_next_article...
})
end
More flexible solution: use some sort of JSON serialization customizers (or roll your own), one I've used before is ActiveModel Serializers (https://github.com/rails-api/active_model_serializers). I'm sure there are others.
Will the user always use the Next and the Prev articles? If so, put them together on the JSON, otherwise, use link location tags with REL to prev and next on the returning JSON, then the clint will be able to know where to get the next and prev articles. You can get more info: RESTful Web Service usage of custom link relations - 'rel'
Here is the solution I came up with:
Implementing two methods in the Entry model object for next and previous as follows:
def next
Entry.where( status_id: 0 ).where( "published_at > ?", self.published_at ).select('id, title, slug').first
end
def previous
Entry.where( status_id: 0 ).where( "published_at < ?", self.published_at ).select('id, title, slug').last
end
and replace respond_with by render :json in the show action of my controller:
def show
#entry = params[:id].to_i != 0 ? Entry.find(params[:id]) : Entry.where(slug: params[:id]).first
render :json => #entry.to_json(:include => :entry_fields,
:methods => [:next, :previous])
end
Output:
{
"id": 20,
"title": "Video Test",
"slug": "video-test",
"status_id": 0,
"promoted": true,
"published_at": "2014-01-20T11:51:00.000Z",
"created_at": "2014-01-20T11:51:37.406Z",
"updated_at": "2014-03-05T13:42:49.981Z",
"excerpt": "Video",
"user_id": "933dc175-0d73-45fb-9437-61ecc4f55705",
"next": {
"id": 21,
"title": "Test Multiple",
"slug": "test-multiple"
},
"previous": {
"id": 18,
"title": "Example Entry",
"slug": "example-entry"
},
...
... (associations)
...
}