Show won't render correct json - ruby-on-rails

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

Related

How to parsed serialize nested hash in ruby on rails?

I have order model which has one serialise column as order_details. When I tries to call index action it returns the hash in order_details key.
I want all the values of order_details in main object of order.
My order model as below:
# models/order.rb
class Order < ActiveRecord::Base
belongs_to :user
serialize :order_details
....
end
Controller
# controllers/orders_controller.rb
class OrdersController < ApplicationController
def index
orders = Order.select('id, user_id, total, order_details')
render json: orders, status: :ok
end
end
The JSON response I received is as below:
[{
"order":{
"id":1,
"user_id":1,
"total": 1000,
"order_details":{
"payment_done_by":"credit/debit",
"transaction_id":"QWERTY12345",
"discount":210,
"shipping_address": "This is my sample address"
}
},
{
"order":{
"id":2,
"user_id":2,
"total": 500,
"order_details":{
"payment_done_by":"net banking",
"transaction_id":"12345QWERTY",
"discount":100,
"shipping_address": "This is my sample address 2"
}
}
]
But here I need response in below format
[{
"order":{
"id":1,
"user_id":1,
"total": 1000,
"payment_done_by":"credit/debit",
"transaction_id":"QWERTY12345",
"discount":210,
"shipping_address": "This is my sample address"
},
{
"order":{
"id":2,
"user_id":2,
"total": 500,
"payment_done_by":"net banking",
"transaction_id":"12345QWERTY",
"discount":100,
"shipping_address": "This is my sample address 2"
}
]
I was trying to parsed each response using each but result can have hundreds of user object.
Please help here.
Thanks in advance.
Krishna
you should add as_json to your Order model to override the existing method with the same name for you to meet your expected output
def as_json(options = nil)
if options.blank? || options&.dig(:custom)
attrs = attributes.slice("id", "user_id", "total")
attrs.merge(order_details)
else
super
end
end
then, in your controller
def index
orders = Order.all
render json: orders, status: :ok
end
hope that helps
FYR: https://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json

Rails nested serializer not using methods to break out keys

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.

How to use nested_attributes when processing JSON?

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.

How to add virtual attributes to API json response

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)
...
}

Active Model Serializers: Adding extra information outside root in ArraySerializer

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
}

Resources