Rails permit nested array - ruby-on-rails

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
}
}
}

Related

Rails permit nested attribute

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.

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 ;)

CarrierWave causing json output to become nested on photo key

I have a weird issue with CarrierWave that I can't find anywhere else.
I'm using Jbuilder to generate the JSON for my API. I have a photos table and a url field on the table.
Without mount_uploader :url PhotoUploader on the photos model, my JSON looks like this:
"photos": [
{
"id": 11,
"url": "https://s3.amazonaws.com/...",
"order": 1
},
{
"id": 12,
"url": "https://s3.amazonaws.com/...",
"order": 2
}
]
But when I add the uploader, my JSON ends up looking like this:
"photos": [
{
"id": 3,
"url": {
"url": {
"url": "https://bucket-name.s3.amazonaws.com/uploads/photos/https%3A//s3.amazonaws.com/bucket-name/uploads/folder/photos/img-name.jpg"
}
},
"order": 2
},
{
"id": 2,
"url": {
"url": {
"url": "https://bucket-name.s3.amazonaws.com/uploads/photos/https%3A//s3.amazonaws.com/bucket-name/uploads/folder/photos/img-name.jpg"
}
},
"order": 1
}
],
Everything works fine, but the JSON is just so messy looking, and a pain to iterate over. Also, can anyone explain why the URL is so weird looking, repeating itself twice?
Jbuilder code:
json.exercises current_user.current_training_week.exercises.uniq do |exercise|
json.id exercise.id
json.name exercise.name
json.description exercise.description
json.photos exercise.photos, :id, :url, :order
json.videos exercise.videos, :id, :url
end
Thanks!
#Arel, if you need to keep same json hash structure, just override :serializable_hash method in your uploader class:
class PhotoUploader < CarrierWave::Uploader::Base
def serializable_hash
model.read_attribute :url
end
end
When you do something like mount_uploader. CarrierWave will override the read column method and replace it with return an object of your uploader.
So when you try to call url on photo it return you an uploader with url method so it will keep calling url, and get the url value. So there are three url in your json.
But you can still use url_url methods get the origin value.
So if you just want to get the specific url, just do like this will be OK.
json.photos exercise.photos, :id, :url_url, :order #url_url is the url column value.

Ember, Ember-data and Rails relations error: "Cannot read property 'typeKey' of undefined"

I connect Ember to a Rails API which delivers some JSON.
I'm trying to get relations working (product hasMany images) but it keeps giving me this error:
Cannot read property 'typeKey' of undefined
My Models:
App.Product = DS.Model.extend({
images: DS.hasMany('image'),
title: DS.attr('string'),
});
App.Image = DS.Model.extend({
product: DS.belongsTo('product')
});
Rails renders json as:
{
"products":[
{
"id": 1,
"title": "product title",
"images":[
{
"id": 1,
"urls":
{
"thumb":"http://domain.com/thumb/image.jpg",
"original":"http://domain.com/original/image.jpg"
}
}
]
}
]
}
Turns out I needed to "sideload" my images in Rails, so the JSON became:
{
"products":[
{
"id": 1,
"title": "product title",
"image_ids": [1]
}
],
"images":[
{
"id": 1,
"urls":
{
"thumb":"http://domain.com/thumb/image.jpg",
"original":"http://domain.com/original/image.jpg"
}
}
]
}
Rails' ProductSerializer:
class ProductSerializer < ActiveModel::Serializer
embed :ids, :include => true
attributes :id, :title
has_many :images
methods :image_urls
end
It seems that you were using embedded JSON in your example. You need to use the EmbeddedRecordsMixin https://github.com/emberjs/data/blob/master/packages/ember-data/lib/serializers/embedded_records_mixin.js and set the appropriate flag to mark images as being embededd

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