How to merge another field in object in rails json response - ruby-on-rails

Json response which I send is like that
"ad": {
"id": 3,
"title": "dgdfg",
"description": "kjlj",
"video_file_name": "SampleVideo_1080x720_1mb.mp4",
"thumbnail_file_name": "images.jpeg",
"campaign_id": null,
"duration": null
},
"video_url": "/system/ads/videos/000/000/003/original/SampleVideo_1080x720_1mb.mp4?1448019186"
I want that video_url also merge with in ad object.
The way I send response now is
render json: {:success=>true, :message=>"Ad detail",:ad=>#ad, :video_url => #ad.video.url}, :status=>200
How I merge it with ad object?
I want to send it like
"ad": {
"id": 3,
"title": "dgdfg",
"description": "kjlj",
"video_file_name": "SampleVideo_1080x720_1mb.mp4",
"thumbnail_file_name": "images.jpeg",
"campaign_id": null,
"duration": null,
"video_url": "/system/ads/videos/000/000/003/original/SampleVideo_1080x720_1mb.mp4?1448019186"
}
My #ad object is
#<Ad:0x007efc20495f98
id: 3,
title: "dgdfg",
description: "kjlj",
video_file_name: "SampleVideo_1080x720_1mb.mp4",
video_content_type: "video/mp4",
video_file_size: 1055736,
video_updated_at: Fri, 20 Nov 2015 11:33:06 UTC +00:00,
thumbnail_file_name: "images.jpeg",
thumbnail_content_type: "image/jpeg",
thumbnail_file_size: 9962,
thumbnail_updated_at: Fri, 20 Nov 2015 11:33:22 UTC +00:00,
created_at: Fri, 20 Nov 2015 11:33:22 UTC +00:00,
updated_at: Fri, 20 Nov 2015 11:33:22 UTC +00:00,
campaign_id: nil,
duration: nil>

First merge {:video_url => #ad.video.url } with #ad then do following:
{:ad => #ad.attributes.merge( :video_url => #ad.video.url )}
so your render call would look like following:
render json: {:success=>true, :message=>"Ad detail", ad: #ad.attributes.merge( :video_url => #ad.video.url )}, :status=>200
You may need to use #ad.attributes.except("created_at",....) at following code if you don't need some of the attributes of your active record object #ad.

Just before render define the object to send (note that if #ad is not a hash, probably it should be converted to hash before):
# ⇓⇓⇓⇓⇓⇓⇓ this depends on what #ad currently is
object_to_send = #ad.to_hash.merge(video_url: #ad.video.url)
and then:
render json: { success: true,
message: "Ad detail",
ad: object_to_send },
status: 200

You could use the as_json method, but you need a method that returns the URL directly
class Ad
def video_url
video.url
end
end
Then in the render
render json: {
success: true,
message: "Ad detail",
ad: ad.as_json(
only: {
:id, :title, :description, :video_file_name, :thumbnail_file_name, :campaign_id, :duration
},
methods: :video_url
),
status: 200
of course if you want you could wrap this into some method,
class Ad
def my_video_json
as_json(
only: {
:id, :title, :description, :video_file_name, :thumbnail_file_name, :campaign_id, :duration
},
methods: :video_url
)
end
end
Then the render would look like this
render json: { success: true, message: "Ad detail", ad: ad.my_video_json }, status: 200

You can add new key and value in hash by adding this:
#ad.attributes[:video_url] = #ad.video.url
I hope this help you.

Related

Update column if has_many field exists

I have two tables business_hours and working_hours.
The models:
class BusinessHour < ApplicationRecord
has_many :working_hours, class_name: "WorkingHour"
belongs_to :organization
accepts_nested_attributes_for(:working_hours, update_only: true)
end
class WorkingHour < ApplicationRecord
belongs_to :business_hour
validates :day, inclusion: { in: %w(mon tue wed thu fri sat sun) }
validate :validate_day, on: :create
def validate_day
if business_hour.working_hours.where(day: self.day).exists?
errors.add(:day, "has already been added")
end
end
end
The controller:
class Api::V1::Admin::BusinessHoursController < Api::BaseController
def update
#organization.build_business_hours unless #organization.business_hours
if #organization.business_hours.update(business_hour_params)
render status: :ok,
json: { notice: I18n.t("resource.update", resource_name: "Organization") }
else
render status: :unprocessable_entity, json: { errors: #organization.business_hours.errors.full_messages }
end
end
private
def business_hour_params
params.require(:business_hours).permit(
:enabled, :away_message, working_hours_attributes: [:day, :start_time, :end_time]
)
end
end
When the business_hours is updated , I'm trying to update the working_hours as well.
The required behaviour is that, the working_hours should be created with day field from mon to friday and each business_hour will have 7 working_hours entry. For example, if a working_hour with day as "mon" already exists for a business_hour, when the update method in controller is called , only the start_time and end_time needs to be updated for the particular working_hour. How to go about this?
Request body example:
{
"business_hours": {
"enabled": true,
"away_message": "Hello",
"working_hours_attributes": [{
"day": "mon",
"start_time": "Tue, 06 Sep 2022 10:07:21.771116000 UTC +00:00",
"end_time": "Tue, 06 Sep 2022 10:07:21.771116000 UTC +00:00"
}]
}
}
As said on the comment:
Well, nested attributes can be updated by using "id" key, if your frontend doesn't have this info you could treat the parameters to convert day: 'mon' to id: id by storing your 'working_hours' into a key-value pair, ..., I don´t think there is a better way of doing it without using this middleware between your update and your permitted parameters
class Api::V1::Admin::BusinessHoursController < Api::BaseController
def update
#organization.build_business_hours unless #organization.business_hours
if #organization.business_hours.update(update_business_hour_params)
render status: :ok,
json: { notice: I18n.t("resource.update", resource_name: "Organization") }
else
render status: :unprocessable_entity, json: { errors: #organization.business_hours.errors.full_messages }
end
end
private
def business_hour_params
params.require(:business_hours).permit(
:enabled, :away_message, working_hours_attributes: [:day, :start_time, :end_time]
)
end
# THIS HASN'T BEEN TESTED, USE IT AS AN EXAMPLE
def update_business_hour_params
update_business_hour_params = business_hour_params
update_business_hour_params[:working_hours_attributes].each do |working_hour_parameters|
working_hour_parameters[:id] = working_hours_day_id_pair[working_hour_parameters.delete(:day)] # Retrieves the id from the day
end
update_business_hour_params
end
def working_hours_day_id_pair
#working_hours_day_id_pair ||= #organization.business_hours.working_hours.pluck(:day, :id).to_h
end
end
as said, this is an example, I could not test the code, but that's the idea
as your attributes is already update_only, you should be good to go, hope this helps you

Rails rendering why have to call .to_json

I work on a Rails API, and I want to render some binary in json. To do that I convert my binary in hex to render it.
So I have
#<PlayCard id: 3, card_id: 12, atk: 10, hp: 9, deck_id: nil, game_id: nil, uid: ".dk\x8A", created_at: "2018-06-06 15:17:25", updated_at: "2018-06-06 15:17:25", user_id: 27>
(byebug) play_card.to_json
{"id"=>3, "card_id"=>12, "atk"=>10, "hp"=>9, "deck_id"=>nil, "game_id"=>nil, "uid"=>"2e646b8a", "created_at"=>Wed, 06 Jun 2018 17:17:25 CEST +02:00, "updated_at"=>Wed, 06 Jun 2018 17:17:25 CEST +02:00, "user_id"=>27}
My question is about the rendering of my object. With my method show I Have no problem but with my method create I have to call my_object.to_json did you have an idea ? With out the .to_json I have a ActionDispatch::TestResponse object.
def show
record = PlayCard.find_by(id: params[:id])
if record.present?
render json: record.attributes.except('uid'), status: :ok
else
render json: {}, status: :no_content
end
end
def create
play_card = PlayCardsService.create(play_card_params)
if play_card.valid?
render json: play_card.to_json, status: :created
else
render json: { status: 'KO', errors: play_card.errors.full_messages }, status: :unprocessable_entity
end
end
class PlayCardsService
class << self
def create(play_card_params)
PlayCard.create(play_card_params)
end
end
end
def to_json(options = {})
bin = bin_to_hex(self.uid)
self.uid = nil
json = self.as_json
json['uid'] = bin
json
end
def bin_to_hex(s)
s.each_byte.map { |b| b.to_s(16).rjust(2,'0') }.join
end
SOLUTION:
I have to override as_json and not to_json. Look the comment of #engineersmnky.
Thanks for your help
Have a nice day,
In show function there is record.attributes, so You get hash after attributes method which can be rendered as json. In create function You are rendering activerecord, where you can use attributes method too or just convert it with to_json. Consider using jbuilder to render json

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
}

How to customize json output in rails?

I have a model for languages and i want to get all the languages as json but the json output looks as follows
[{"language":{"created_at":null,"id":1,"language":"English","updated_at":null}},{"language":{"created_at":null,"id":2,"language":"Swedish","updated_at":null}},{"language":{"created_at":null,"id":3,"language":"German","updated_at":null}},{"language":{"created_at":null,"id":4,"language":"French","updated_at":null}},{"language":{"created_at":null,"id":5,"language":"spanish","updated_at":null}},{"language":{"created_at":null,"id":6,"language":"dutch","updated_at":null}},{"language":{"created_at":"2012-12-03T05:01:18Z","id":7,"language":"Tamil","updated_at":"2012-12-03T05:01:18Z"}}]
but i want to make this as
{"language":[{"created_at":null,"id":1,"language":"English","updated_at":null},{"created_at":null,"id":2,"language":"Swedish","updated_at":null},{"created_at":null,"id":3,"language":"German","updated_at":null},{"created_at":null,"id":4,"language":"French","updated_at":null},{"created_at":null,"id":5,"language":"spanish","updated_at":null},{"created_at":null,"id":6,"language":"dutch","updated_at":null},{"created_at":null,"id":7,"language":"Tamil","updated_at":null} ] }
Update
def index
#languages = Language.all
respond_to do |format|
format.json { render json: #languages}
end
end
update 2
class Language < ActiveRecord::Base
ActiveRecord::Base.include_root_in_json = false
has_and_belongs_to_many :users
end
I believe this should work:
format.json { render json: { "language" => #languages.as_json(:root => false) }.to_json }
What this does it to convert the #languages array into an array of JSON-formatted hash models with no root keys (using as_json), then wraps the result in a hash with a root key "language", and convert that hash into a JSON-formatted string with to_json. (See the docs for details on including or not including a root node using as_json.)
For example, with a model Post:
posts = Post.all
#=> [#<Post id: 1, name: "foo", title: "jkl", content: "some content", created_at: "2012-11-22 01:05:46", updated_at: "2012-11-22 01:05:46">]
# convert to array of hashes with no root keys
posts.as_json(root: false)
#=> [{"content"=>"some content", "created_at"=>Thu, 22 Nov 2012 01:05:46 UTC +00:00, "id"=>1, "name"=>"foo", "title"=>"jkl", "updated_at"=>Thu, 22 Nov 2012 01:05:46 UTC +00:00}]
# add root back to collection:
{ "post" => posts.as_json(root: false) }
#=> {"post"=>[{"content"=>"some content", "created_at"=>Thu, 22 Nov 2012 01:05:46 UTC +00:00, "id"=>1, "name"=>"foo", "title"=>"jkl", "updated_at"=>Mon, 03 Dec 2012 09:41:42 UTC +00:00}]}
# convert to JSON-formatted string
{ "post" => posts.as_json(root: false) }.to_json
#=> "{\"post\":[{\"content\":\"some content\",\"created_at\":\"2012-11-22T01:05:46Z\",\"id\":1,\"name\":\"foo\",\"title\":\"jkl\",\"updated_at\":\"2012-12-03T09:43:37Z\"}]}"
override the as_json on the Model you want to customize
def as_json options={}
{
id: id,
login: login,
name: custom.value, #for custom name
...
}
end
==> or
def as_json(options={})
super(:only => [:id, :login, :name ....])
end
from : here
Other link: here
I suggest you to use rabl gem (https://github.com/nesquena/rabl) to format your data.
Override as_json method in your model, to include associations, hide columns and why not? calling custom methods as they were attributes
def as_json(options={})
super(:except => [:created_at,:updated_at],
:include => {
:members => {
:only => [:role, :account],
:include => {
:account => {
:only => [:name, :subdomain]
}
}
}
},
:methods => [:jwt_token]
)
end
This will output something like this:
{
"id": 2,
"name": "Test Teacher",
"email": "teacher#testing.io",
"jwt_token":"eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MiwiZXhwIjoxNTY2NzQ0OTQzfQ.HDGu7JiJEQEEpGo7inuXtOZBVQOfTaFquy8dr-QH5jY",
"members": [{
"role": "instructor",
"account": {
"name": "Testing",
"subdomain": "test"
}
}],
}
The easiest way of adding custom json output when you render json is by using gem that provide many json templates-
https://github.com/fabrik42/acts_as_api

to_json and to_xml giving different outputs in rails

Beginning rails programmer here. I'm trying to convert a hash to both xml and json, but the output is different.
Here's the hash:
{:exchangeRates=>[{:baseCurrency=>"USD", :quoteCurrency=>"EUR", :amount=>1, :nprices=>1, :conversions=>[{:date=>Tue, 20 Nov 2012 21:00:00 +0000, :ask=>"0.7813", :bid=>"0.7813"}]}, {:baseCurrency=>"CAD", :quoteCurrency=>"EUR", :amount=>1, :nprices=>1, :conversions=>[{:date=>Tue, 20 Nov 2012 21:00:00 +0000, :ask=>"0.7839", :bid=>"0.7837"}]}]}
Here's the corresponding render code
format.json { render :json => { :response => rates.to_hash() } }
and here's the JSON (which is what I want)
{"response": {"exchangeRates": [
{
"baseCurrency": "USD",
"quoteCurrency": "EUR",
"amount": 1,
"nprices": 1,
"conversions": [{
"date": "2012-11-20T21:00:00+00:00",
"ask": "0.7813",
"bid": "0.7813"
}]
},
{
"baseCurrency": "CAD",
"quoteCurrency": "EUR",
"amount": 1,
"nprices": 1,
"conversions": [{
"date": "2012-11-20T21:00:00+00:00",
"ask": "0.7839",
"bid": "0.7837"
}]
}
]}}
Here's my xml render code:
format.xml { render :xml => rates.to_hash(), :root => 'response' }
Here's the xml output (there are extra tags where I put in arrays):
<response>
<exchangeRates type="array">
<exchangeRate>
<baseCurrency>USD</baseCurrency>
<quoteCurrency>EUR</quoteCurrency>
<amount type="integer">1</amount>
<nprices type="integer">1</nprices>
<conversions type="array">
<conversion>
<date type="datetime">2012-11-20T21:00:00+00:00</date>
<ask>0.7813</ask>
<bid>0.7813</bid>
</conversion>
</conversions>
</exchangeRate>
<exchangeRate>
<baseCurrency>CAD</baseCurrency>
<quoteCurrency>EUR</quoteCurrency>
<amount type="integer">1</amount>
<nprices type="integer">1</nprices>
<conversions type="array">
<conversion>
<date type="datetime">2012-11-20T21:00:00+00:00</date>
<ask>0.7839</ask>
<bid>0.7837</bid>
</conversion>
</conversions>
</exchangeRate>
</exchangeRates>
</response>
As you can see, it is adding the extra "array" attribute tags, i.e. exchangeRates and conversions. How do I get this to format the same as the json? I also don't want the attributes on any of the tags either. I know you can pass in attributes, such as :root => 'response', but after looking for quite some time, I can't seem to find a listing of these attributes on the web.
Any help would be greatly appreciated, thanks!
This is a case where it's best just to go directly to the source code. The to_xml method is in the ActiveModel::Serializer module, here are the inline docs, which don't mention anything about the type="array" attribute tags. Dig a bit deeper though and you'll see they appear in the same file on line 130 of a method called add_associations.
rails/activemodel/lib/active_model/serializers/xml.rb:130
type = options[:skip_types] ? { } : {:type => "array"}
That tells us that there's an option called skip_types, which appears to be documented nowhere. Try passing that to to_xml, and you get the desired behaviour:
a = [1, 2, 3]
a.to_xml
#=> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<fixnums type=\"array\">\n <fixnum type=\"integer\">1</fixnum>\n <fixnum type=\"integer\">2</fixnum>\n <fixnum type=\"integer\">3</fixnum>\n</fixnums>\n"
a.to_xml(:skip_types => true)
#=> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<fixnums>\n <fixnum>1</fixnum>\n <fixnum>2</fixnum>\n <fixnum>3</fixnum>\n</fixnums>\n"
You'll notice all the added type attributes are gone.
So just pass the same option to render and you'll get the desired result:
format.xml { render :xml => rates.to_hash(), :root => 'response', :skip_types => true }

Resources