Rails API JSON response, change name of method - ruby-on-rails

I have a Rails 5 API that renders an object with some of it's methods to JSON.
render json: { rides: #rides }.to_json( :methods => [ :is_preferred ]), status: 200
So this returns something like:
{
id: 123,
is_preferred: true
}
But I would like to change the name of the attribute that refers to the is_preferred method.
The output I would like id:
{
id: 123,
preferred: true
}
I tried
render json: { rides: #rides }.to_json( :methods => [ preferred: :is_preferred ]), status: 200
But this does not work. Easiest would be to change the method name in the model, but that's not possible in this case.
Is there any way I can manipulate the name inside the response?

You can try with an ActiveModel::Serializer, then you can define the attribute key as you want
class RideSerializer < ActiveModel::Serializer
attribute :preferred, key: :is_preferred
end
or use a method to retrieve the attribute value
class RideSerializer < ActiveModel::Serializer
attribute :is_preferred
def is_preferred
object.preferred
end
end
Using serializers has a lot of bennefits and is so powerfull if you want to create custom responses.

Related

ActiveModel Serializer JSON Includes "attributes" Key

I'm using the Rails ActiveModel Serializer to output JSON to an API endpoint.
I'm getting this JSON output:
{
"data":[
{
"id":"396",
"type":"profiles",
"attributes":{
"name":"Impossibles",
"created-at":"2017-05-11T18:14:06.201-04:00",
"updated-at":"2018-04-01T13:34:15.905-04:00",
"website":"http://soundcloud.com/impossibles"
}
}
]
}
But was expecting it to be formatted like this:
{
"data":[
{
"id":"396",
"type":"profiles",
"name":"Impossibles",
"created-at":"2017-05-11T18:14:06.201-04:00",
"updated-at":"2018-04-01T13:34:15.905-04:00",
"website":"http://soundcloud.com/impossibles"
}
]
}
Trying to avoid the extra level of nesting in the returned JSON.
Is there a way to remove the "attributes" key?
This is my serializer:
class ProfileSerializer < ActiveModel::Serializer
attributes :id, :name, :created_at, :updated_at, :website
end
And my controller:
def show
profile = Profile.find(params[:id])
render json: profile, status: :ok
end
After reading through some GitHub issues, it appears the "attributes" nesting is coming from the JSON API spec and is expected behavior:
https://jsonapi.org/format/#document-resource-objects
This issue was helpful:
https://github.com/rails-api/active_model_serializers/issues/2202
Looks like a feature, not a bug.

Rails 5 ActiveRecord, how to include fields from associated tables?

With Rails 5, Given the models:
chef_positions
* id
* name
skills
* id
* name
chef_position_skills
* id
* chef_position_id
* skill_id
I have a controller method to return the chef_position_skills by chef_position_id:
def index
chef_position = ChefPosition.find(params[:chef_position_id])
render json: chef_position.chef_position_skills
end
This returns:
[{"id":1,"chef_position_id":2,"skill_id":1,"created_at":"2017-06-05T15:44:06.821Z","updated_at":"2017-06-05T15:44:06.821Z"},{"id":2,"chef_position_id":2,"skill_id":2,"created_at":"2017-06-05T15:44:06.821Z","updated_at":"2017-06-05T15:44:06.821Z"}]
How can I get the controller to do the following:
include skill.name for each record
Do not include the timestamps
you need to associate the two first if you haven't already, in your model, chef_positions.rb
has_many :skills, through: :chef_position_skills
Then in your controller,
ChefPosition.where(id: params[:chef_position_id]).joins(:skills).select('chef_positions.id, skills.name')
def index
chef_position = ChefPosition.find(params[:chef_position_id])
render json: chef_position.chef_position_skills.map(&:json_data)
end
# ChefPositionSkill#json_data
def json_data
to_json(
include: { skill: { only: [:name] } },
only: [:id, :chef_position_id, :skill_id]
)
end
Define a method json_data (just for convenience), and use .map to call it for each chef_position_skill.
The include and only are standard json serializer methods, which assist rails in what needs to be included.
The only drawback (as far as I see), is that now you will have another attribute "skill": { "name": "skill_name" } in your final json.
Use
render json: chef_position.chef_position_skills.
map {|s| s.slice(:id, :chef_position_id, :skill_id).merge s.skill.slice(:name) }
I don't have the same models but here is a similar example:
irb(main):026:0> u.slice(:id, :email).merge u.funds.slice(:min)
=> {"id"=>1, "email"=>"test#example.com", "min"=>1000000}
But I think you'll really like JBuilder which is included in Rails 5.
https://github.com/rails/jbuilder

How to return a json "object" instead of a json "array" with Active Model ArraySerializer?

Using Active Model Serializer, is there an easy and integrated way to return a JSON "object" (that would then be converted in a javascript object by the client framework) instead of a JSON "array" when serializing a collection? (I am quoting object and array, since the returned JSON is by essence a string).
Let's say I have the following ArticleSerializer:
class ArticleSerializer < ActiveModel::Serializer
attributes :id, :body, :posted_at, :status, :teaser, :title
end
I call it from ArticlesController:
class ArticlesController < ApplicationController
def index
#feed = Feed.new(articles: Article.all)
render json: #feed.articles, each_serializer: ArticleSerializer
end
end
Is there a way to pass an option to the serializer to make it return something like:
{"articles":
{
"1":{
...
},
"2":{
...
}
}
}
instead of
{"articles":
[
{
"id":"1",
...
},
{
"id":"2"
...
}
]
}
Edit: I guess that the approach proposed in this post (subclassing AMS ArraySerializer) might be helpful (Active Model Serializer and Custom JSON Structure)
You'd have to write a custom adapter to suit your format.
Alternatively, you could modify the hash before passing it to render.
If you do not mind iterating over the resulting hash, you could do:
ams_hash = ActiveModel::SerializableResource.new(#articles)
.serializable_hash
result_hash = ams_hash['articles'].map { |article| { article['id'] => article.except(:id) } }
.reduce({}, :merge)
Or, if you'd like this to be the default behavior, I'd suggest switching to the Attributes adapter (which is exactly the same as the Json adapter, except there is no document root), and override the serializable_hash method as follows:
def format_resource(res)
{ res['id'] => res.except(:id) }
end
def serializable_hash(*args)
hash = super(*args)
if hash.is_a?(Array)
hash.map(&:format_resource).reduce({}, :merge)
else
format_resource(hash)
end
end
No, semantically you are returning an array of Articles. Hashes are simply objects in Javascript, so you essentially want an object with a 1..n method that returns each Article, but that would not make much sense.

Return associated model attribute as current model attribute

I have an association between ProcessingStatus and Order that looks like this:
Order belongs_to ProcessingStatus
ProcessingStatus has_one Order
When I return an Order, I want to be able to get the ProcessingStatus 'name' attribute as the 'status' attribute of my JSON response.
So, right now, for the following GET call at /orders/73,
render :json => #order.to_json(:only => [:id], :include => {:processing_status => {:only => [:name]}})
I get this:
{
"id": 73,
"processing_status": {
"name": 'processing'
}
}
And I am looking for a way to get this:
{
"id": 73,
"status": 'processing'
}
Anyway on doing that?
You could define a method on your model that returns the status as the value of processing_status.name and then include that in your json:
class Order < ActiveRecord::Base
def status
self.processing_status.try(:name)
end
end
And then include that in your to_json call:
#order.to_json(only: :id, methods: :status)
Alternatively you could add the status to a hash that gets converted to json:
#order.as_json(only: :id).merge(status: #order.processing_status.try(:name)).to_json
I've used .try(:name) in case processing_status is nil but you might not need that. The behaviour is slightly different in the two cases as the first will not include status in the json and the second will include "status":null.

How do I add extra data to ActiveRecord records being serialized to JSON in Rails?

I'm exposing some resources via a simple API that returns JSON. I would like to inject the path to each of the resources so that the consumer need not construct them. Example of desired output for something like User.all.to_json:
users: [
{user: {
id: 1,
name: 'Zaphod',
url: 'http://domain.com/users/1.json'
}},
{user: {
id: 2,
name: 'Baron Munchausen',
url: 'http://domain.com/users/2.json'
}}
];
In order to generate the URL I'd like to continue using the helpers and not pollute the models with this kind of information. Is there a way to do this? Or am I better off just putting this into the model?
If you have a model method you want included in the json serialization you can just use the builtin to_json call with the :methods parameter:
class Range
def url
# generate url
...
end
end
Range.find(:first).to_json(:methods => :url)
Have you checked this out : http://json.rubyforge.org/ ?
class Range
def to_json(*a)
{
'json_class' => self.class.name,
'data' => [ first, last, exclude_end? ]
}.to_json(*a)
end
def self.json_create(o)
new(*o['data'])
end
end

Resources