CarrierWave causing json output to become nested on photo key - ruby-on-rails

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.

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

How to have two pieces of data output as json in rabl

New to rabl and not sure how to do this with two different arrays returned in a single hash like this:
#data={:locations => [location1, location2], :items => [item1,item2]}
In my rabl file, I'd like to do something like the following:
#data[:locations]
extends "api/location_show"
#data[:items]
extends "api/item_show"
to output this:
{
"locations": [
{
"id": 156,
"name": "Location 1"
},
{
"id": 158,
"name": "Location 2"
}
],
"items": [
{
"global_id": 3189,
"header": "pistachio 1"
},
{
"global_id": 3189,
"header": "pistachio 2"
}
]
}
but it just doesn't seem to be working. Is there a way to get this to work?
thx
Your rabl file should look something like:
object false
child (:locations) { attributes :id, :name }
child (:items) { attributes :global_id, :header }
By setting object to false, you essentially tell rabl that you want to construct your nodes on your own. Then you can go ahead and invoke the child and node methods as you wish.

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