Rails rendering why have to call .to_json - ruby-on-rails

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

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

Params for json array in Rails

I'm new to rails,
Please check my code and tell me whats wrong with my use params, because this is how it made sense to me.
Controller:
def create
user = User.find(user_params)
order = user.purchases.new
render json: order.errors if !order.save
basket = params.require(:basket)
basket.each do |b|
i = Item.find(b[:item_id])
render json: i.errors, status: 422 if !i
order.purchases_items.create(item_id: i, quantity: b[:quantity])
end
render nothing: true, status: 201 # location: show action
end
and my test file is sending
test "making order" do
post "/api/users/#{#tuser.id}/orders",
{ basket: [ { item_id: '2', quantity: '5' },
{ item_id: '1', quantity: '4'} ] }.to_json,
{ 'Accept' => Mime::JSON, 'Content-Type' => Mime::JSON.to_s }
assert_response 201
assert_equal Mime::JSON, response.content_type
end
Thanks,
What I basically want to do is store each array element in the array basket from params[:basket], and iterate over it.
Sometime params keys are not get converted into symbols automatically. Can u try passing string "basket" instead of symbol :basket?

How to merge another field in object in rails json response

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.

How do I return a database object as a hash

Why does my application return #users = User.where(acceptance: true) as #<User:0x007f9b0d444328>?
when the console returns the same query as:
[#<User id: 1, acceptance: "t", created_at: "2012-09-27 13:01:50", updated_at: "2012-09-27 13:02:52">]
I want the users as a hash to pass to this sort of thing:
respond_to do |format|
format.html
format.csv { render text: #users.to_csv }
end
#user.attributes
#users.map { |user| user.attributes }
This is a ruby hash of the fields in your database. Then it will be up to you to encode it in JSON or CSV.

How do i get the before_save value in the controller in Rails 3?

def update
#product_category = #business_category.product_categories.find(params[:id])
product_category_was = #business_category.product_categories.find(params[:id])
respond_to do |format|
if #product_category.update_attributes(params[:product_category])
share_associations(#product_category, product_category_was) if in_params('_maps_attributes', 'product_category')
format.js
format.html { redirect_to(admin_product_categories_path, :notice => 'Product category was successfully updated.') }
format.xml { head :ok }
else
format.js
format.html { render :action => "edit" }
format.xml { render :xml => #product_category.errors, :status => :unprocessable_entity }
end
end
end
The function share_associations has the parameters #product_category and product_category_was. The problem is, when i call product_category_was.send('images') for example (which i have to call using send since the call is dynamic) it obviously pulls the newest associated images and not the images that were associated. Is there anyway i can get the object to get the images that were associated at the point in time it was made?
I think you need a deep copy of your object, because normal association (=) will only create a reference:
product_category_was = Marshal::load(Marshal::dump(#product_category))
This might not work for all kinds of objects, but for normal Rails objects this should work.
I have no idea what arnep is talking about, nor what problem you're getting. What you're doing works for me on two different finds through an association, and so it should.
irb(main):016:0> s = School.first
=> #<School id: 2, name: "Bar", created_at: "2011-04-09 17:48:57", updated_at: "2011-05-13 09:13:38", confirmed: nil, zipcode: nil>
irb(main):017:0> g1 = s.grades.find 4
=> #<Grade id: 4, name: "4th", type: nil, school_id: 2, created_at: "2011-04-19 03:17:49", updated_at: "2011-05-13 09:15:17">
irb(main):018:0> g2 = s.grades.find 4
=> #<Grade id: 4, name: "4th", type: nil, school_id: 2, created_at: "2011-04-19 03:17:49", updated_at: "2011-05-13 09:15:17">
irb(main):019:0> g1.update_attributes :name => '5th'
=> true
irb(main):020:0> g2
=> #<Grade id: 4, name: "4th", type: nil, school_id: 2, created_at: "2011-04-19 03:17:49", updated_at: "2011-05-13 09:15:17">
irb(main):021:0> g1
=> #<Grade id: 4, name: "5th", type: nil, school_id: 2, created_at: "2011-04-19 03:17:49", updated_at: "2011-05-13 09:16:02">
irb(main):022:0>
In fact, usually people are asking the inverse question - how to get an already instantiated object to reload from the DB. The problem is probably in your share_associations method, or something else you're not showing yet.
I found a way to do something that works for now. It's not the greatest way, but it works for now. I basically created an empty array and pushed the product_categories array into it. This made it so the value was no longer a call so the value does not change. Hopefully this will help someone else eventually.
def update
#product_category = #business_category.product_categories.find(params[:id])
if in_params('_maps_attributes', 'product_category')
media_was = Array.new
media_was = media_was | #business_category.product_categories.find(params[:id]).send(map_type('product_category').pluralize)
end
respond_to do |format|
if #product_category.update_attributes(params[:product_category])
share_associations(#product_category, media_was) if in_params('_maps_attributes', 'product_category')
format.js
format.html { redirect_to(admin_product_categories_path, :notice => 'Product category was successfully updated.') }
format.xml { head :ok }
else
format.js
format.html { render :action => "edit" }
format.xml { render :xml => #product_category.errors, :status => :unprocessable_entity }
end
end
end

Resources