Rails 5.1.: strong params for JSON POST request - ruby-on-rails

I have JS where data are posted with Ajax and in terminal my params look like this:
Started POST "/strongbolt/user_groups" for 10.0.2.2 at 2017-06-27 16:27:23 +0000
Processing by Strongbolt::UserGroupsController#create as JSON
Parameters: {"strongbolt_user_group"=>{"name"=>"Some test group",
"description"=>"Some test description", "user_ids"=>{"0"=>{"id"=>"3"},
"1"=>{"id"=>"2"}, "2"=>{"id"=>"5"}}, "role_ids"=>{"0"=>{"id"=>"1"},
"1"=>{"id"=>"2"}}}}
My Create action looks like this:
def create
user_roles #Helper method
#user_group = Strongbolt::UserGroup.create!(user_group_params)
respond_to do |format|
format.js { flash.now[:notice] = "User group #{#user_group.name} created!" }
format.json { render json: {
data: #user_group.as_json(only: [:id, :name, :description], include: {
users: { only: [:id, :name] }, roles: {only: [:id, :name] }}),
}
}
end
end
private
def user_group_params
params.require(:strongbolt_user_group)
.permit(:name, :description, {user_ids: []}, {role_ids: []})
end
In my terminal I can see it create name and description, but does not insert user_ids and role_ids. So far I've been trying differently with params, but no luck - can't make them to be saved.
I get this error: Unpermitted parameters: :user_ids, :role_ids
How do I make all params to be saved, please? Thank you!

instead of
{
"strongbolt_user_group"=>{
"name"=>"Some test group",
"description"=>"Some test description",
"user_ids"=>{"0"=>{"id"=>"3"}, "1"=>{"id"=>"2"}, "2"=>{"id"=>"5"}},
"role_ids"=>{"0"=>{"id"=>"1"}, "1"=>{"id"=>"2"}}
}
}
you should send
{
"strongbolt_user_group"=>{
"name"=>"Some test group",
"description"=>"Some test description",
"user_ids"=>["3", "2", "5"],
"role_ids"=>["1", "2"]
}
}
EDIT
If you can't change the format of received params, you could do something like the following:
private
def user_group_params
modified_params.require(:strongbolt_user_group)
.permit(:name, :description, {user_ids: []}, {role_ids: []})
end
def modified_params
user_ids = params[:strongbolt_user_group][:user_ids].values.map(&:values).flatten
role_ids = params[:strongbolt_user_group][:role_ids].values.map(&:values).flatten
ActionController::Parameters.new({
strongbolt_user_group: params[:strongbolt_user_group].except(:user_ids, role_ids).merge(user_ids: user_ids, role_ids: role_ids)
})
end

Related

ActionController::ParameterMissing (param is missing or the value is empty: vendor

I am trying to make a post using Postman on my Rails app and I get an issue saying:
ActionController::ParameterMissing (param is missing or the value is empty: vendor
Did you mean? vendor_data
action
quote_type
controller):
This is my controller file where the params are called:
def create_vendor_params
params.require(:vendor).permit(
:vendor_id, :quote_type, :first_name, :last_name, :email, :phone_type, :phone, :request_type, :address1, :address_city, :address_state, :address_zip, :DOB
)
end
And this is the data I am sending from Postman. Now I am not sure if it's because renter_data it's nested and on my params permit:
{
"vendor_id": 2,
"quote_type": "renter",
"renter_data": {
"first_name":"Tony",
"last_name":"Stark",
"email":"tony#starkindustries",
"phone_type":"mobile",
"phone":"6504881234",
"request_type":"renter",
"address1":"123 Main Street",
"address_city":"Palo Alto",
"address_state":"CA",
"address_zip":"94301",
"DOB":"1990-07-22"
}
}
Is this issue because of nested data inside renter_data? And if it is, how can I nest it?
EDIT
When I make the post I see an empty object at the end of the request:
Processing by Api::V2::LeadController#create as */*
Parameters: {"vendor_id"=>2, "quote_type"=>"renter", "renter_data"=>{"first_name"=>"Tony", "last_name"=>"Stark", "email"=>"tony#starkindustries", "phone_type"=>"mobile", "phone"=>"6504881234", "request_type"=>"renter", "address1"=>"123 Main Street", "address_city"=>"Palo Alto", "address_state"=>"CA", "address_zip"=>"94301", "DOB"=>"1990-07-22"}, "vendor"=>{} <---HERE }
Why is that?
There are two problems: First, the strong params expect the input to be nested under vendor. Second, the strong params do not expect a nested renter_data hash.
To fix this in your query change the structure of the request data to:
{
"vendor": {
"vendor_id": 2,
"quote_type": "renter",
"first_name":"Tony",
"last_name":"Stark",
"email":"tony#starkindustries",
"phone_type":"mobile",
"phone":"6504881234",
"request_type":"renter",
"address1":"123 Main Street",
"address_city":"Palo Alto",
"address_state":"CA",
"address_zip":"94301",
"DOB":"1990-07-22"
}
}
If you want to keep the nested renter_data hash then you have to change the strong params like Paarth Agrawal already suggested. But then you still need to nest everything under vendor.
This is the concept of nested strong params in create_vender_params function you have to add more permitted params
params.require(:vendor).permit(
:vendor_id, :quote_type, renter_Data: [:first_name, :last_name, :email, :phone_type, :phone, :request_type, :address1, :address_city, :address_state, :address_zip, :DOB])
vendor_table has some fields vendor_id, quote_type, first_name, last_name, email,phone_type,phone, request_type, address1,address_city,address_state, address_zip,DOB.
def create_vendor_params
params.require(:vendor).permit(
:vendor_id, :quote_type, :first_name, :last_name, :email,
:phone_type,:phone, :request_type, :address1, :address_city,
:address_state,:address_zip, :DOB)
end
"vendor": {
"vendor_id": 2,
"quote_type": "renter",
"first_name":"Tony",
"last_name":"Stark",
"email":"tony#starkindustries",
"phone_type":"mobile",
"phone":"6504881234",
"request_type":"renter",
"address1":"123 Main Street",
"address_city":"Palo Alto",
"address_state":"CA",
"address_zip":"94301",
"DOB":"1990-07-22"
}
def create
#vendor = Vendor.new(create_vendor_params)
if #vendor.save
render json: {'Success' }
else
render json: {error: #vendor.errors}
end
end

Rails - 5: How do I pass an array (has_many association) to the controller action

I have a model event and another model event_rule
class Event < ApplicationRecord
has_many :event_rules
end
class EventRule < ApplicationRecord
belongs_to :event
end
I have written an api event#create for saving an event. Here's the body of the POST request:
{
"name": "asd",
"code": "ad",
"isActive": true,
"description": "asd",
"notes": "",
"goalAmount": 0,
"exportId": "",
"defaultCurrency": 1,
"eventStartDate": "2017-04-25T18:30:00.000Z",
"eventEndDate": "2017-04-27T18:30:00.000Z",
"eventRules": [
{
"extraInformation": "{}",
"lookupKeyValuePairId": 40
}
]
}
Here's params hash:
Parameters: {"name"=>"asd", "code"=>"ad", "is_active"=>true, "description"=>"asd", "notes"=>"", "goal_amount"=>0, "export_id"=>"", "default_currency"=>1, "event_start_date"=>"2017-04-25T18:30:00.000Z", "event_end_date"=>"2017-04-27T18:30:00.000Z", "event_rules"=>[{"extra_information"=>"{}", "lookup_key_value_pair_id"=>40}], "client_id"=>"0", "event"=>{"name"=>"asd", "code"=>"ad", "description"=>"asd", "is_active"=>true, "goal_amount"=>0, "export_id"=>"", "event_start_date"=>"2017-04-25T18:30:00.000Z", "event_end_date"=>"2017-04-27T18:30:00.000Z", "default_currency"=>1, "notes"=>""}}
I want the 'event_rules' to be included INSIDE the event. How can do this?
def create
# initialize Event object with `event_params`
event = Event.new(event_params)
# initialize EventRule object per each `event_rule_params`, and associate the EventRule as part of `event.event_rules`
event_rules_params.each do |event_rule_params|
event.event_rules << EventRule.new(event_rule_params)
end
if event.save
# SUCCESS
else
# FAILURE
end
end
private
def event_params
params.require(:event).permit(:name, :code, :is_active, :description, :notes, :goal_amount, :export_id, :default_currency, :event_start_date, :event_end_date, :notes)
end
def event_rules_params
params.require(:event).fetch(:event_rules, []).permit(:extra_information, :lookup_key_value_pair_id)
end
Alternative Rails-way Solution:
if you have control over the parameters that get sent, reformat your request into something like the following (take note of changing event_rules into event_rules_attributes -- Rails Standard) (More Info Here)
Parameters: {
"event"=>{
"name"=>"asd",
"code"=>"ad",
"description"=>"asd",
"is_active"=>true,
"goal_amount"=>0,
"export_id"=>"",
"event_start_date"=>"2017-04-25T18:30:00.000Z",
"event_end_date"=>"2017-04-27T18:30:00.000Z",
"default_currency"=>1,
"notes"=>"",
"event_rules_attributes"=>[
{
"extra_information"=>"{}",
"lookup_key_value_pair_id"=>40
}
]
}
}
# controllers/events_controller.rb
def create
event = Event.new(event_params)
if event.save
# SUCCESS
else
# FAILURE
end
end
private
def event_params
params.require(:event).permit(:name, :code, :is_active, :description, :notes, :goal_amount, :export_id, :default_currency, :event_start_date, :event_end_date, :notes, event_rules_attributes: [:extra_information, :lookup_key_value_pair_id])
end
# models/event.rb
accepts_nested_attributes_for :event_rules

How to use nested_attributes when processing JSON?

I'm trying to write an update method that processes JSON. The JSON looks like this:
{
"organization": {
"id": 1,
"nodes": [
{
"id": 1,
"title": "Hello",
"description": "My description."
},
{
"id": 101,
"title": "fdhgh",
"description": "My description."
}
]
}
}
Organization model:
has_many :nodes
accepts_nested_attributes_for :nodes, reject_if: :new_record?
Organization serializer:
attributes :id
has_many :nodes
Node serializer:
attributes :id, :title, :description
Update method in the organizations controller:
def update
organization = Organization.find(params[:id])
if organization.update_attributes(nodes_attributes: node_params.except(:id))
render json: organization, status: :ok
else
render json: organization, status: :failed
end
end
private
def node_params
params.require(:organization).permit(nodes: [:id, :title, :description])
end
I also tried adding accepts_nested_attributes_for to the organization serializer, but that does not seem to be correct as it generated an error (undefined method 'accepts_nested_attributes_for'), so I've only added accepts_nested_attributes_for to the model and not to the serializer.
The code above generates the error below, referring to the update_attributes line in the update method. What am I doing wrong?
no implicit conversion of String into Integer
In debugger node_params returns:
Unpermitted parameters: id
{"nodes"=>[{"id"=>101, "title"=>"gsdgdsfgsdg.", "description"=>"dgdsfgd."}, {"id"=>1, "title"=>"ertret.", "description"=>"etewtete."}]}
Update: Got it to work using the following:
def update
organization = Organization.find(params[:id])
if organization.update_attributes(nodes_params)
render json: organization, status: :ok
else
render json: organization, status: :failed
end
end
private
def node_params
params.require(:organization).permit(:id, nodes_attributes: [:id, :title, :description])
end
To the serializer I added root: :nodes_attributes.
It now all works, but I'm concerned about including the id in node_params. Is that safe? Wouldn't it now be possible to edit the id of the organization and node (which shouldn't be allowed)? Would the following be a proper solution to not allowing it to update the id's:
if organization.update_attributes(nodes_params.except(:id, nodes_attributes: [:id]))
looks super close.
Your json child object 'nodes' need to be 'nodes_attributes'.
{
"organization": {
"id": 1,
"nodes_attributes": [
{
"id": 1,
"title": "Hello",
"description": "My description."
},
{
"id": 101,
"title": "fdhgh",
"description": "My description."
}
]
}
}
You can do this sort of thing. Put this in your controller.
before_action do
if params[:organization]
params[:organization][:nodes_attributes] ||= params[:organization].delete :nodes
end
end
It will set the correct attribute in params and still use all the accepts_nested_attributes features.

Strong params when POSTing multiple items to Rails API

I am trying to create multiple records via a POST request to the Rails API but am having issues with the params side of things.
What is happening is the item is not being created as the callbacks in the model are looking for values that are not present. I believe this has something to do with the params.
Here is my code:
def batch_create
params[:product].each do |p|
product = Product.new(batch_create_product_params)
product.save
end
respond_to do |format|
format.json { render json: #product }
end
end
def batch_create_product_params
params.permit(:name, :link, :category_old, :image_url, :price, :interest, :revenue, :end_date, :company, :country, :above_average_revenue)
end
The raw contents of my json call are:
{
"product": [
{
"name": "Strap On SoftRack Roof Rack by Otium Acupressure",
"link": "https://things.com/89902997836713867011111211111121/Strap-On-SoftRack-Roof-Rack-by-Otium",
"category_old": "",
"image_url": "https://thingd-media-ec1.com/default/899029978367138670_42120cf10765.jpg",
"price": "160",
"interest": "9999",
"company": "ACME",
"country": "USA",
"revenue": "999999"
},
{
"name": "Strap On SoftRack Roof Rack by Otium Acupressure 2",
"link": "https://things.com/Strap-On-SoftRack-Roof-Rack-by-Otium",
"category_old": "",
"image_url": "https://thingd-media-ec1.com/default/899029978367138670_42120cf10765.jpg",
"price": "160",
"interest": "9999",
"company": "ACME",
"country": "USA",
"revenue": "999999"
}
]
}
The resulting params are then as follows:
The total parameters when the call is first made.
Parameters: {"product"=>[{"name"=>"Strap On SoftRack Roof Rack by Otium Acupressure", "link"=>"https://fancy.com/things/89902997836713867011111211111121/Strap-On-SoftRack-Roof-Rack-by-Otium", "category_
old"=>"", "image_url"=>"https://thingd-media-ec1.thefancy.com/default/899029978367138670_42120cf10765.jpg", "price"=>"160", "interest"=>"9999", "company"=>"Fancy", "country"=>"USA", "revenue"=>"999999"},
{"name"=>"Strap On SoftRack Roof Rack by Otium Acupressure 2", "link"=>"https://fancy.com/things/Strap-On-SoftRack-Roof-Rack-by-Otium", "category_old"=>"", "image_url"=>"https://thingd-media-ec1.thefancy.
com/default/899029978367138670_42120cf10765.jpg", "price"=>"160", "interest"=>"9999", "company"=>"Fancy", "country"=>"USA", "revenue"=>"999999"}], "import"=>{}}
The params once they are in the loop.
{"name"=>"Strap On SoftRack Roof Rack by Otium Acupressure", "link"=>"https://fancy.com/things/89902997836713867011111211111121/Strap-On-SoftRack-Roof-Rack-by-Otium", "category_old"=>"", "i
mage_url"=>"https://thingd-media-ec1.thefancy.com/default/899029978367138670_42120cf10765.jpg", "price"=>"160", "interest"=>"9999", "company"=>"Fancy", "country"=>"USA", "revenue"=>"999999"}
I believe what you're looking for is more like this. Placing the root product key in the permit call tells rails that this is going to be an array of objects, containing the keys noted.
def batch_create
batch_create_product_params[:product].each do |product_params|
product = Product.new(product_params)
product.save
end
respond_to do |format|
format.json { render json: #product }
end
end
def batch_create_product_params
params.permit(product: [:name, :link, :category_old, :image_url, :price, :interest, :revenue, :end_date, :company, :country, :above_average_revenue])
end
I don't think new takes an array. I think you would need to do this - use the p param in the strong parameters.
def batch_create
params[:product].each do |p|
product = Product.new(batch_create_product_params(p)) # Pass p
product.save
end
respond_to do |format|
format.json { render json: #product }
end
end
def batch_create_product_params(p) # Accept params
p.permit(:name, :link, :category_old, :image_url, :price, :interest, :revenue, :end_date, :company, :country, :above_average_revenue)
end
create does take an array. Maybe this would do in your case.
def batch_create
product = Product.create(batch_create_product_params[:product])
respond_to do |format|
format.json { render json: #product }
end
end
def batch_create_product_params
params.permit(product: [:name, :link, :category_old, :image_url, :price, :interest, :revenue, :end_date, :company, :country, :above_average_revenue])
end

Dealing with array while using to_json

I have just started learning ruby on rails and I was using this code to get the json representation of the question model
post "api/quizzes/addquestion/#{#quiz.id}",
{ question:
{ text: "Question Text Example", mark: 2, choices:["a","b","c","d"], right_answer: "a" }
}.to_json
I got :
choices: [] , empty array
I don't know the reason for this as all other attributes are sent succesfully. What is the solution for such problem
I am using this while testing the following method:
def add_question
question = Question.new(question_params)
quiz = Quiz.find(params[:quiz_id])
if question.save
quiz.questions << question
render json: { success: true, data:{:question => question}, info:{} }, status: 201
else
render json: { success: false, data:{}, :info => question.errors }, status: 422
end
end
def question_params
params.require(:question).permit(:text, :mark, :choice, :right_answer)
end
the test :
class AddingQuestionsTest < ActionDispatch::IntegrationTest
setup {#quiz = Quiz.create(name: 'Quiz1', subject: 'physics', duration: 10, no_of_MCQ: 5, no_of_rearrangeQ: 5)}
setup {#question = Question.create(text: "Question Text Example", mark: 2, choices:["a","b","c","d"], right_answer: "a")}
test 'successes to add question to a quiz' do
post "api/quizzes/addquestion/#{#quiz.id}",
{ question:
{ text: "Question Text Example", mark: 2, choices:["a","b","c","d"], right_answer: "a" }
}.to_json,
{ 'Accept' => Mime::JSON, 'Content-Type' => Mime::JSON.to_s }
question_response = json(response.body)
assert_equal 201, response.status
assert_equal #quiz.questions.first, #question
assert_equal publish_response[:success], true
end
end
and this is the question model:
class Question < ActiveRecord::Base
serialize :choices,Array
belongs_to :quiz
end
When I run the test I get failure in:assert_equal #quiz.questions.first, #question
choices: []
choices: ["a", "b", "c", "d"]
Just we need to change
def question_params
params.require(:question).permit(:text, :mark, :choice, :right_answer)
end
to
def question_params
params.require(:question).permit(:text, :mark, :right_answer, :choice => [])
end

Resources