There are at least 10 questions on this topic but none of them answer this particular issue. Many of the questions relate to Rails forms like this, which I don't have, or to json structures that are more complicated, like this or this.
EDIT regarding the accepted answer and why this is not an exact duplicate
The linked question in the answer from @CarlosRoque initially looks to be the same problem but it only solves the Rails side of this particular issue.
If you read all the comments you will see multiple attempts at changing the template_params
method were made to RENAME or REPLACE the nested attribute "template_items" with "template_items_attributes". This is necessary because Rails accepts_nested_attributes_for
requires "_attributes" to be appended to the name, otherwise it cannot see it.
If you examine the monkey patch code in that answer to fix wrap_parameters so that it works for nested attributes, you still have the problem that it won't actually find "template_items" (the nested object) because it does not have the suffix "_attributes".
Therefore to fully solve this the client also had to be modified to send the nested object as "template_items_attributes". For JS clients this can be done by implementing a toJSON() method on the object to modify it during serialization (example here). But be aware that when you deserialize the JSON, you will need to manually create an instance of that object for toJSON() to work (explained why here).
I have a simple has_many / belongs_to:
Models:
class Template < ApplicationRecord
belongs_to :account
has_many :template_items
accepts_nested_attributes_for :template_items, allow_destroy: true
end
class TemplateItem < ApplicationRecord
belongs_to :template
validates_presence_of :template
enum item_type: {item: 0, heading: 1}
end
The json sent from the client looks like this:
{
"id": "55e27eb7-1151-439d-87b7-2eba07f3e1f7",
"account_id": "a61151b8-deed-4efa-8cad-da1b143196c9",
"name": "Test",
"info": "INFO1234",
"title": "TITLE1",
"template_items": [
{
"is_completed": false,
"item_type": "item"
},
{
"is_completed": false,
"item_type": "heading"
}
]
}
Sometimes there will be an :id
and a :content
attribute in each template_item (eg. after they have been created and user starts editing them).
The template_params
method of the templates_controller
looks like this:
params.require(:template).permit(
:id, :account_id, :name, :title, :info,
template_items: [:id, :is_completed, :content, :item_type]
)
If this was a Rails form then that line would be:
params.require(:template).permit(
:id, :account_id, :name, :title, :info,
template_items_attributes: [:id, :is_completed, :content, :item_type]
)
for saving the nested children objects as part of the parent template update action.
I tried changing the nested param name:
def template_params
params.require(:template).permit(:id, :account_id, :name, :title, :info, template_items: [:id, :is_completed, :content, :item_type])
params[:template_items_attributes] = params.delete(:template_items) if params[:template_items]
Rails.logger.info params
end
and I can see they are still not permitted:
{
"template" =><ActionController::Parameters {
"id" =>"55e27eb7-1151-439d-87b7-2eba07f3e1f7",
"account_id" =>"a61151b8-deed-4efa-8cad-da1b143196c9",
"name" =>"Test",
"info" =>"INFO1234",
"title" =>"TITLE1",
} permitted:false >,
"template_items_attributes" => [
<ActionController::Parameters {
"is_completed" =>false,
"item_type" =>"item"
} permitted:false >,
<ActionController::Parameters {
"is_completed" =>false,
"item_type" =>"item"
} permitted:false >
]
}
I also tried merging:
template_params.merge! ({template_items_attributes:
params[:template_items]}) if params[:template_items].present?
Same problem.
So how can I ensure they are permitted and included in template_params WITHOUT just doing .permit! (ie. I don't want to permit everything blindly)?
The controller update method:
def update
Rails.logger.info "*******HERE*******"
Rails.logger.info template_params
@template.template_items = template_params[:template_items_attributes]
if @template.update(template_params)
render json: @template
else
render json: ErrorSerializer.serialize(@template.errors), status: :unprocessable_entity
end
end
UDPATE
If I send from the client "template_items_attributes" instead of "template_items" inside the parameters to Rails, and then do the recommended template_params like this:
def template_params
params.require(:template).permit(:id, :account_id, :name, :title, :info, template_items_attributes: [:id, :is_completed, :content, :item_type])
end
it still does not create new children for the template!
With this in place, I output the parameters before and afterwards, like this:
def update
Rails.logger.info params
Rails.logger.info "*******HERE*******"
Rails.logger.info template_params
if @template.update(template_params)
render json: @template
else
render json: ErrorSerializer.serialize(@template.errors), status: :unprocessable_entity
end
end
And here is the log from this scenario - Rails is STILL completely ignoring the embedded array. Notice that params, just before HERE, shows permitted: false and then afterwards template_params no longer contains the children "template_items_attributes" and is marked permitted:true.
I, [2017-10-20T21:52:39.886104 #28142] INFO -- : Processing by Api::TemplatesController#update as JSON
I, [2017-10-20T21:52:39.886254 #28142] INFO -- : Parameters: {"id"=>"55e27eb7-1151-439d-87b7-2eba07f3e1f7", "account_id"=>"a61151b8-deed-4efa-8cad-da1b143196c9", "name"=>"Test", "info"=>"INFO12345", "title"=>"TITLE1", "created_at"=>"2017-10-14T19:30:41.450Z", "updated_at"=>"2017-10-20T17:48:24.909Z", "template_items_attributes"=>[{"is_completed"=>false, "item_type"=>"item"}], "template"=>{"id"=>"55e27eb7-1151-439d-87b7-2eba07f3e1f7", "account_id"=>"a61151b8-deed-4efa-8cad-da1b143196c9", "name"=>"Test", "info"=>"INFO12345", "title"=>"TITLE1", "created_at"=>"2017-10-14T19:30:41.450Z", "updated_at"=>"2017-10-20T17:48:24.909Z"}}
D, [2017-10-20T21:52:39.903011 #28142] DEBUG -- : User Load (7.7ms) SELECT "users".* FROM "users" WHERE "users"."uid" = $1 LIMIT $2 [["uid", "rmcsharry+owner@gmail.com"], ["LIMIT", 1]]
D, [2017-10-20T21:52:40.072148 #28142] DEBUG -- : Template Load (1.4ms) SELECT "templates".* FROM "templates" WHERE "templates"."id" = $1 ORDER BY name ASC LIMIT $2 [["id", "55e27eb7-1151-439d-87b7-2eba07f3e1f7"], ["LIMIT", 1]]
I, [2017-10-20T21:52:40.083727 #28142] INFO -- : <ActionController::Parameters {"id"=>"55e27eb7-1151-439d-87b7-2eba07f3e1f7", "account_id"=>"a61151b8-deed-4efa-8cad-da1b143196c9", "name"=>"Test", "info"=>"INFO12345", "title"=>"TITLE1", "created_at"=>"2017-10-14T19:30:41.450Z", "updated_at"=>"2017-10-20T17:48:24.909Z", "template_items_attributes"=>[{"is_completed"=>false, "item_type"=>"item"}], "controller"=>"api/templates", "action"=>"update", "template"=>{"id"=>"55e27eb7-1151-439d-87b7-2eba07f3e1f7", "account_id"=>"a61151b8-deed-4efa-8cad-da1b143196c9", "name"=>"Test", "info"=>"INFO12345", "title"=>"TITLE1", "created_at"=>"2017-10-14T19:30:41.450Z", "updated_at"=>"2017-10-20T17:48:24.909Z"}} permitted: false>
I, [2017-10-20T21:52:40.083870 #28142] INFO -- : *******HERE*******
D, [2017-10-20T21:52:40.084550 #28142] DEBUG -- : Unpermitted parameters: :created_at, :updated_at
I, [2017-10-20T21:52:40.084607 #28142] INFO -- : <ActionController::Parameters {"id"=>"55e27eb7-1151-439d-87b7-2eba07f3e1f7", "account_id"=>"a61151b8-deed-4efa-8cad-da1b143196c9", "name"=>"Test", "title"=>"TITLE1", "info"=>"INFO12345"} permitted: true>
D, [2017-10-20T21:52:40.084923 #28142] DEBUG -- : Unpermitted parameters: :created_at, :updated_at
D, [2017-10-20T21:52:40.085375 #28142] DEBUG -- : (0.2ms) BEGIN
D, [2017-10-20T21:52:40.114015 #28142] DEBUG -- : Account Load (1.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT $2 [["id", "a61151b8-deed-4efa-8cad-da1b143196c9"], ["LIMIT", 1]]
D, [2017-10-20T21:52:40.131895 #28142] DEBUG -- : Template Exists (0.8ms) SELECT 1 AS one FROM "templates" WHERE "templates"."name" = $1 AND ("templates"."id" != $2) AND "templates"."account_id" = 'a61151b8-deed-4efa-8cad-da1b143196c9' LIMIT $3 [["name", "Test"], ["id", "55e27eb7-1151-439d-87b7-2eba07f3e1f7"], ["LIMIT", 1]]
D, [2017-10-20T21:52:40.133754 #28142] DEBUG -- : (0.3ms) COMMIT
D, [2017-10-20T21:52:40.137763 #28142] DEBUG -- : CACHE Account Load (0.0ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT $2 [["id", "a61151b8-deed-4efa-8cad-da1b143196c9"], ["LIMIT", 1]]
D, [2017-10-20T21:52:40.138714 #28142] DEBUG -- : (0.2ms) BEGIN
D, [2017-10-20T21:52:40.141293 #28142] DEBUG -- : User Load (1.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 FOR UPDATE [["id", "88de3be9-6d18-4687-ab80-d50f78638ca9"], ["LIMIT", 1]]
D, [2017-10-20T21:52:40.235163 #28142] DEBUG -- : Account Load (0.7ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT $2 [["id", "a61151b8-deed-4efa-8cad-da1b143196c9"], ["LIMIT", 1]]
D, [2017-10-20T21:52:40.240997 #28142] DEBUG -- : SQL (1.4ms) UPDATE "users" SET "tokens" = $1, "updated_at" = $2 WHERE "users"."id" = $3 [["tokens", "{\"ryyymFZ7fpH50rMKArjZ2Q\":{\"token\":\"$2a$10$4jkgRe4LBPxJ8fQUOKCSausUi7DbIUD0bE.7ZRoOuTHrRuX6CaWOe\",\"expiry\":1509293414,\"last_token\":\"$2a$10$cpI.mz81JFjQT0J9acCCl.NdrEatI5l17GtrwrAfwyhyN3xRExcaC\",\"updated_at\":\"2017-10-15T17:10:16.996+02:00\"},\"Y2y0maUT5WYSfH6VZeORag\":{\"token\":\"$2a$10$8KERiIwlc3rX.Mdu.CW6wOMLDbVyB2PFCaBIlw7/LUxC3ITpYTISW\",\"expiry\":1509293475,\"last_token\":\"$2a$10$r6Xw6798T1P7UZlTbEaXoeBCl9oK2fMs72ppAtars8Ai/kaE6nE66\",\"updated_at\":\"2017-10-15T17:11:18.066+02:00\"},\"9Cy48CPVj3WhFkEBPUZQ1Q\":{\"token\":\"$2a$10$Qy4JOD8.jIcPhf93MqFCIelnVaA/ssE31w5DlL8MShDuMROsLSNuS\",\"expiry\":1509293942,\"last_token\":\"$2a$10$e6sxklrHRRD1C15Ix/MqQOfACuCMznmzUjF296cpO1ypWVvJ.JFJK\",\"updated_at\":\"2017-10-15T17:19:05.200+02:00\"},\"O5iufW0Gacqs9sIfJ9705w\":{\"token\":\"$2a$10$EkDf7.y3lY9D36lAwNHBGuct97M6/HGDvnrUsD72c8zCsfVd8y9c2\",\"expiry\":1509482450,\"last_token\":\"$2a$10$S0kHEvKxom2Qgdy0r.q0aeTSlSBFkqU4XZeY91n3RkkYkQykmmGVi\",\"updated_at\":\"2017-10-17T21:40:50.300+02:00\"},\"ETOadoEtoxcz6rR6Ced_dA\":{\"token\":\"$2a$10$8t01bWv/PsVojs3cazuSg..FWa9SZwq1/PUDfuN1S4yBxnMFv2zre\",\"expiry\":1509742360,\"last_token\":\"$2a$10$hveuajISXDOjHLm9EkVzvOd3pwKkqE1rQnIFBoojf0vgMLXV2EvVe\",\"updated_at\":\"2017-10-20T21:52:40.233+02:00\"}}"], ["updated_at", "2017-10-20 19:52:40.236607"], ["id", "88de3be9-6d18-4687-ab80-d50f78638ca9"]]
D, [2017-10-20T21:52:40.243960 #28142] DEBUG -- : (1.3ms) COMMIT
I, [2017-10-20T21:52:40.244504 #28142] INFO -- : Completed 200 OK in 358ms (Views: 1.0ms | ActiveRecord: 37.7ms)