I'm passing nested JSON into rails like so:
{
"product": {
"vendor": "Acme",
"categories":
{
"id": "3",
"method": "remove",
},
"categories":
{
"id": "4"
}
}
}
in order to update the category on a product. I am trying to iterate through the categories attribute in my products_controller so that I can add/remove the product to multiple categories at once:
def updateCategory
#product = Product.find(params[:id])
params[:product][:categories].each do |u|
#category = Category.find_by(id: params[:product][:categories][:id])
if params[:product][:categories][:method] == "remove"
#product.remove_from_category(#category)
else
#product.add_to_category(#category)
end
end
end
However, this only uses the second 'categories' ID in the update and doesn't iterate through both.
Example response JSON:
{
"product": {
"id": 20,
"title": "Heavy Duty Aluminum Chair",
"product_price": "47.47",
"vendor": "Acme",
"categories": [
{
"id": 4,
"title": "Category 4"
}
]
}
}
As you can see, it only added the category with ID = 4, and skipped over Category 3.
I'm fairly new to rails so I know I'm probably missing something obvious here. I've played around with the format of the JSON I'm passing in as well but it only made things worse.
You need to change your JSON structure. As you currently have it, the second "categories" reference will override the first one since you can only have 1 instance of a key. To get what you want, you should change it to:
{
"product": {
"vendor": "Acme",
"categories": [
{
"id": "3",
"method": "remove",
},
{
"id": "4"
}
]
}
}
You will also need to change your ruby code to look like:
def updateCategory
#product = Product.find(params[:id])
params[:product][:categories].each do |u|
#category = Category.find_by(id: u[:id])
if u[:method] == "remove"
#product.remove_from_category(#category)
else
#product.add_to_category(#category)
end
end
end
Related
I am working on rails 6 with ruby-2.6.5 and i am working on the API. I am using nested attributes for my order as follows:-
orders_controller.rb
# frozen_string_literal: true
module Api
module V1
class OrdersController < Api::V1::ApiApplicationController
before_action :validate_token
def create
debugger
order = OrderInteractor.create(order_params, #user_id)
if order.save
render json: { 'message' => 'Order Placed' }, status: :ok
else
render_errors(order)
end
end
private
def order_params
params.require(:data)
.require(:attributes)
.require(:order)
.permit(:user_id, :address_id, :total_price, :payment_status,
:order_number, :delivery_time_slot,
order_details_attributes:
%i[price quantity order_detail_status product_id
order_number variant_id],
payment_details_attributes:
%i[payment_data payment_id])
end
end
end
end
Api Request:-
{
"data": {
"attributes": {
"order": {
"address_id": "82",
"delivery_time_slot": "5:00 PM - 8:00 PM(Today)",
"order_details_attributes": [{
"price": "76.0",
"product_id": "46",
"quantity": "4",
"variant_id": "47"
}, {
"price": "9.9",
"product_id": "30",
"quantity": "1",
"variant_id": "29"
}],
"payment_details_attributes": [{
"payment_data": {
"data": {
"nameValuePairs": {
"razorpay_payment_id": "pay_HiHceX2p6450Wa",
"org_logo": "",
"org_name": "Razorpay Software Private Ltd",
"checkout_logo": "https://cdn.razorpay.com/logo.png",
"custom_branding": false
}
},
"paymentId": "pay_HiHceX2p6450Wa",
"userContact": "+916494949494",
"userEmail": "dailyferia#gmail.com"
}
}],
"total_price": "354"
}
},
"type": "orders"
}
}
While placing order i am getting the error Unpermitted parameter: :payment_data but it's working fine for the order_details. Please help me to fix it? I also tried the below ways to fix it but nothing worked:-
payment_details_attributes: %i[:payment_data payment_id]) and `payment_details_attributes: ['payment_data', 'payment_id'])`
Your payment_data is a complex object, rather than the scalars that are found in your order_details_attributes
You will need to add more to the permitted parameters, I believe the simplest solution would be:
payment_details_attributes: [payment_data: {}]
This should accept all parameters under payment_details_attributes, but it would also permit any other keys as well. You may want to be more strict and only allow the parameters specified above, in which case you could do:
payment_details_attributes: [
payment_data: {
data: {
nameValuePairs:
%i[razorpay_payment_id org_logo org_name checkout_logo custom_branding]
},
:paymentId, :userContact, :userEmail
}
]
which should restrict the parameters to just the format used in your example.
A few other notes:
You have %i[payment_data payment_id] in your original sample, but there is no payment_id in your payload. The attribute in the sample is paymentId, and on top of that, it is an attribute of the payment_data, not the payment_details_attributes
you wouldn't use %i and a colon, the %i is a shorthand for creating an array of ruby symbols, so %i[:payment_data payment_id] would create the array [:":payment_data", :payment_id] (note the extra colon at the beginning of payment_data)
Lastly, I haven't tested my code above, so there could be a syntax or other error, but hopefully this points you in the right direction.
I have an association of my model Banner with Images, I need to bring all the images but only the field file, in file I have a hash in which I just want to get the url
I have this code:
Banner.find_by(event_id: #event.id).to_json(:include => [{:images => {:only => [:file]}}])
but this gets me this:
{
"id": 2,
"created_at": "2019-04-24T14:59:08.000-05:00",
"updated_at": "2019-04-24T14:59:08.000-05:00",
"event_id": 3,
"name": "ccccccccccccccccccccccssssss",
"images": [
{
"file": {
"url": "/uploads/image/file/300aecf6-b3c7-4b15-94a1-45c530efc4c4.png"
}
}
]
}
I want something like this:
{
"id": 2,
"created_at": "2019-04-24T14:59:08.000-05:00",
"updated_at": "2019-04-24T14:59:08.000-05:00",
"event_id": 3,
"name": "ccccccccccccccccccccccssssss",
"images": [
"/uploads/image/file/300aecf6-b3c7-4b15-94a1-45c530efc4c4.png"
]
}
How could I do this? Any suggestions?
Sometimes it's best to break things up into smaller pieces...
# create the query to get all the objects to prevent n+1 queries
banners = Banner.includes(images: :file)
# get the banner you want to serialize
banner = banners.find_by(event_id: #event.id)
# get all the urls for the images as an array
image_urls = banner.images.collect {|i| i.file.url }
# create the json object
json = banner.to_json.merge(images: image_urls)
I want to get a specific output from the Typeform API.
This is the response I get back.
Example response:
"answers": [
{
"field": {
"id": "hVONkQcnSNRj",
"type": "dropdown",
"ref": "my_custom_dropdown_reference"
},
"type": "text",
"text": "Job opportunities"
},
{
"field": {
"id": "RUqkXSeXBXSd",
"type": "yes_no",
"ref": "my_custom_yes_no_reference"
},
"type": "boolean",
"boolean": false
}
]
Why does .first work and why does .second not work ?
My OrdersController.rb
items = response.parsed_response["items"]
items.each do |item|
#order = current_user.orders.find_or_create_by(landing_id: item["landing_id"]) do |order|
item["answers"].each do |answer|
order.landing_id = item["landing_id"]
order.email = item["hidden"]["email"]
order.price = item["hidden"]["price"]
order.moduls = item["hidden"]["moduls"]
order.project = item["hidden"]["project"]
order.website = answer.first # This works
order.payment = answer.second # undefined method `second' for #<Hash:0x11f83e78>
end
end
end
You can do
answers.each { |answer| answer[:field] }
or, if you want ids for example
answers.map { |answer| answer.dig(:field, :id) }
Because ruby hash doesn't have any second or last methods. You can access value with the help of keys. e.g. answer[:type], answer[:text]
item["answers"].each do |answer| was an overkill. The solution was as simple as that:
order.website = item["answers"][1]["text] # Access the first field of answers array
order.payment = item["answers"][2]["text] # Access the second field of answers array
I have a ruby (2.4.0p0) rails (5.0.2) controller from which I wish to return a json result containing a list of Thing objects as well as some high level info (such as next and previous from Kaminari paging).
Consider a Thing with an association to Owner. Thing has a owner_id attribute.
For #things = Thing.page(1).per(2) I will be able to use
render json: #things
and get;
[
{ "id": 1, "owner_id": 1, "name": "thing1" },
{ "id": 2, "owner_id": 1, "name": "thing2" }
]
Good. If I then create a serializer called ThingSerializer.rb and define owner such that it adds "owner":"CatInHat" instead of "owner_id":1
This works as well;
[
{ "id": 1, "owner": "CatInHat", "name": "thing1" },
{ "id": 2, "owner": "CatInHat", "name": "thing2" }
]
This is good, but, my problem comes when I want to add higher level data and label the list as "results" such as when I try;
render json: { next:"some_url_link",previous:"some_other_url_link", results: #bags}
I'd like to get;
{ "next":some_url_link,
"prev":some_other_url_link,
"results":[ { "id": 1, "owner": "CatInHat", "name": "thing1" }, { "id": 2, "owner": "CatInHat", "name": "thing2" } ]
}
What I get is nearly the above but with "owner_id":1 instead of "owner":"CatInHat" - my serializer does not seem to be used when I label and nest my list of things. What is the appropriate way to use my serializer and get this output?
If I create config/initializers/active_model_serializers.rb and add
ActiveModel::Serializer.config.adapter = :json_api
It gives me an api which is similar but I don't know if it can be customized to fit the spec I need above.
thank you for any help
It looks like the serialization logic in render json: ... only kicks in if the attribute is an ActiveRecord object or an array of ActiveRecord objects. Since you are giving it a hash, it will not inspect the individual attributes and recursively apply the serializers.
You can try manually applying ThingSerializer:
render json: {
next: ...,
prev: ...,
results: #things.map { |thing|
ThingSerializer.new(thing).attributes
},
}
I have some JSON that looks like this. I have it stored and read into an object, #items.
[
{
{
"id": "A",
"description": "a_description"
},
{
"id": "B",
"description": "b_description"
}
},
{
{
"id": "A",
"description": "a_description"
},
{
"id": "B",
"description": "b_description"
}
}
]
My goal is to display a table with two columns, one labeled A and the other labeled B, in which each row gives the "a_description" and "b_description". I'm not sure how to go about doing this.
Ah, the ol' array of hashes and hashes of arrays problem.
To get around your "out of order" problem you first have to convert
{
"id": "A",
"description": "foo"
},
{
"id": "B",
"description": "bar"
}
into {"A" : "foo", "B" : "bar" }.
#new_items = #items.map do |item|
output = {}
item.each do |hash|
output.merge!(hash["id"] => hash["description"])
end
end
Then #new_items becomes (intentionally presented out of order since hash elements are not ordered)
[
{
"A": "a1_description",
"B": "b1_description"
},
{
"B": "b2_description",
"A": "a2_description"
}
]
From there, each line is simply a hash, so you can just dereference the value you need based on the column you're in.
#new_items.each do |item|
puts "#{item['A']} is paired with #{item['B']}"
end
Keys, of course could be retrieved dynamically if you don't want to hard code "A" and "B" using .keys
Something like this maybe
<tr><th>A</th><th>B</th></tr>
<% #items.each do |item| %>
<tr><td><%=item[0].description%></td><td><%=item[1].description%></td></tr>
<% end %>