Rails permit nested attribute - ruby-on-rails

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.

Related

Rails 4 - Iterate through nested JSON params

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

Rails permit nested array

I am trying to use accepts_nested_attributes_for in conjunction with a has_many association and having a lot of trouble...
Here is a simplified version of my user.rb:
class User < ActiveRecord::Base
...
has_many :user_permissions
accepts_nested_attributes_for :user_permissions
...
end
My user_permission.rb:
class UserPermission < ActiveRecord::Base
belongs_to :user
...
end
And my users_controller.rb:
class UsersController < ApiController
...
def update
#user.assign_attributes user_params
if #user.save
render partial: 'user', locals: { user: #user }
else
render json: {errors: #user.errors}.to_json, status: 500
end
end
...
private
def user_params
params.require(:user).permit(:first_name, :last_name, user_permissions_attributes: [ :user_id, :resource_id, :can_read, :can_update, :can_create, :can_delete ])
end
end
I am referencing this rails documentation on how to use accepts_nested_attributes_for with Strong Parameters.
However, when I 'puts user_params' from inside the users_controller this is all I see (no reference to the user_permissions):
{"first_name"=>"Joe", "last_name"=>"Shmoe"}
Here is an example of JSON I am submitting to the server (via angular $resource):
{
"id": 10,
"first_name": "Joe",
"last_name": "Shmoe",
"user_permissions": [
{
"organization_resource_id": 20,
"user_id": 10,
"can_update": true,
"can_read": true
},
{
"organization_resource_id": 21,
"user_id": 10,
"can_create": true,
"can_read": true
}
],
}
Which returns this JSON:
{
"id": 10,
"first_name": "Joe",
"last_name": "Shmoe",
"user_permissions": [],
}
I am fairly confident this is an issue in my rails layer, but just for reference here is the angular User.js service I created to perform this RESTful interaction with the server:
angular.module('slics').service('User', [
'$resource', function($resource) {
return $resource('/api/users/:id', {
id: '#id'
}, {
update: {
method: 'PUT',
isArray: false
}
});
}
]);
Really not sure what I am missing here. It does not seem like it should be this difficult to submit nested attributes... but the more research I do the more I realize this does seem to be a pretty common Rails frustration.
Please feel free to comment if any additional context/information would be useful to include in my problem description to help troubleshoot this problem and I would be happy to provide it!
Strong params expects user_permissions_attributes, and you're submitting user_permissions.
Strong params is separate from accepts_nested_attributes_for (in fact, it has nothing to do with it), so however you define your require!/permit calls is exactly how your attributes should be submitted.
ProTip: To save you some future frustration, if you plan on updating through accepts nested attributes, you probably want to permit :id as well.
Well, you post an array of hashes, not a hash.
So this code
user_permissions_attributes: [ :user_id, :resource_id, :can_read, :can_update, :can_create, :can_delete ]
will permit such structure
{
"id": 10,
"first_name": "Joe",
"last_name": "Shmoe",
"user_permissions_attributes": [
"organization_resource_id": 20,
"user_id": 10,
"can_update": true,
"can_read": true
]
}
Try to whitelist all params at "user_permissions"
user_permissions_attributes: []
Or check out this article, to learn how to build advanced whitelists with StrongParams
http://patshaughnessy.net/2014/6/16/a-rule-of-thumb-for-strong-parameters
user_permissions_attributes: [ :user_id, :id, :can_read, :can_update, :can_create, :can_delete ]) permit :id and submitting hashes with index value..
JSON format submitting to the serve
"user": {
"id": 10,
"first_name": "Joe",
"last_name": "Shmoe",
"user_permissions": {
"0": {
"id": 20,
"user_id": 10,
"can_update": true,
"can_read": true
},
"1": {
"id": 21,
"user_id": 10,
"can_create": true,
"can_read": true
}
}
}

Rails way for creating an endpoint accepting multiple objects at once

I need to create an endpoint that accepts multiple objects at once. Example request (json body) below:
{
"obd_bluetooth_events" : [
{
"car_id": 1,
"obd_plugin_id": "1",
"kind": "CONNECT",
"date": 1422369149
},
{
"car_id": 1,
"obd_plugin_id": "1",
"kind": "DISCONNECT",
"date": 1422369149
},
{
"car_id": 1,
"obd_plugin_id": "1",
"kind": "CONNECT",
"date": 1422369149
}
]
}
So in order to be able to pass an array to create method: #obd_bluetooth_event.create(obd_bluetooth_events_params)
I need to define obd_bluetooth_events_params method like this:
def obd_bluetooth_events_params
params.permit(
obd_bluetooth_events: [
:car_id,
:obd_plugin_id,
:kind,
:date
]
)[:obd_bluetooth_events]
end
After calling which i get:
Unpermitted parameters: obd_bluetooth_event
=> [{"car_id"=>1, "obd_plugin_id"=>"1", "kind"=>"CONNECT", "date"=>1422369149},
{"car_id"=>1, "obd_plugin_id"=>"1", "kind"=>"DISCONNECT", "date"=>1422369149},
{"car_id"=>1, "obd_plugin_id"=>"1", "kind"=>"CONNECT", "date"=>1422369149}]
Im wondering wether there is a more railsy way to permit an array of objects?
def obd_params
params.require(:myparams).permit(:myarray => [])
end
Works fine for me to permit arrays.
Hope this helps ;)

Shopify API Variant not being returned with product_id attribute

So I'm using the Shopify Gem to access the Shopify API and noticed that the product_id attribute is not being returned within the response body for a simple ShopifyAPI::Variant.find call.
1.9.3p194> ShopifyAPI::Variant.find(209901733)
=> #<ShopifyAPI::Variant:0x007fbf7225d3f0 #attributes={"barcode"=>nil, "compare_at_price"=>"198.00", "created_at"=>"2012-03-23T14:11:39+11:00", "fulfillment_service"=>"manual", "grams"=>1000, "id"=>209901733, "inventory_management"=>"shopify", "inventory_policy"=>"deny", "option1"=>"38", "option2"=>"Ivory Mini Twill", "option3"=>nil, "position"=>16, "price"=>"198.00", "requires_shipping"=>true, "sku"=>"3063", "taxable"=>true, "title"=>"38 / Ivory Mini Twill", "updated_at"=>"2013-04-24T10:25:27+10:00", "inventory_quantity"=>2}, #prefix_options={}, #persisted=true>
According to the new documentation that has been published here, the product_id field should be returned.
GET /admin/variants/#{id}.json
Hide Response
HTTP/1.1 200 OK
{
"variant": {
"barcode": "1234_pink",
"compare_at_price": null,
"created_at": "2013-05-01T15:35:21-04:00",
"fulfillment_service": "manual",
"grams": 200,
"id": 808950810,
"inventory_management": "shopify",
"inventory_policy": "continue",
"option1": "Pink",
"option2": null,
"option3": null,
"position": 1,
"price": "199.00",
"product_id": 632910392,
"requires_shipping": true,
"sku": "IPOD2008PINK",
"taxable": true,
"title": "Pink",
"updated_at": "2013-05-01T15:35:21-04:00",
"inventory_quantity": 10
}
}
It is in the json, but not in the ActiveResource that is created from the json. The reason is this code in the Variant activeresource:
self.prefix = "/admin/products/:product_id/"
def self.prefix(options={})
options[:product_id].nil? ? "/admin/" : "/admin/products/#{options[:product_id]}/"
end
If you want you can make your own class for fetching singleton Variants:
module ShopifyAPI
class VariantWithProduct < Base
self.prefix = "/admin/"
self.element_name = "variant"
self.collection_name = "variants"
end
end
and use this class to fetch single variants by id:
ShopifyAPI::VariantWithProduct.find(xxxxxx)
Michael is correct in his diagnosis of the problem. For me, the easiest way around this was to get the product resource instead of the variant. The ShopifyAPI::Product ActiveResource object does include variants.
product = ShopifyAPI::Product.find(product_id)
variant = product.variants.find { |v| v.id == variant_id }

in rails json rendering, how to show a different key name for a particular attribute

I am using Mongoid as my backend and I am in need to return json with an "id" attribute instead of the default "_id" used by mongoid
for instance, I have now
[{
"_id": "4f2d8b971773eb18e6000001",
"name": "Scooter"
}, {
"_id": "4f2d8d9f1773eb18fd000001",
"name": "Coldplay"
}]
from a call to render:
format.json { render :json => #groups, only:[:name, :_id] }
but need,
[{
"id": "4f2d8b971773eb18e6000001",
"name": "Scooter"
}, {
"id": "4f2d8d9f1773eb18fd000001",
"name": "Coldplay"
}]
Any shortcuts?
Thank you!!
If you're able to add an attribute accessor for _id called just id, then this should be easily solved by overriding as_json in your model.
def id
self._id
end
def as_json(options={})
options.merge!(:except => :_id, :methods => :id)
super(options)
end
Update: Made the override a bit more friendly to the parent method.

Resources