Rails 4.0 Strong Parameters nested attributes with a key that points to a hash
Solution 1
My other answer was mostly wrong - new answer.
in your params hash, :filename is not associated with another hash, it is associated with an ActiveDispatch::Http::UploadedFile object. Your last code line:
def screenshot_params
params.require(:screenshot).permit(:title, assets_attributes: :filename)
is actually correct, however, the filename attribute is not being allowed since it is not one of the permitted scalar types. If you open up a console, and initialize a params object in this shape:
params = ActionController::Parameters.new screenshot: { title: "afa", assets_attributes: {"0" => {filename: 'a string'}}}
and then run it against your last line:
p = params.require(:screenshot).permit(:title, assets_attributes: :filename)
# => {"title" => "afa", "assets_attributes"=>{"0"=>{"filename"=>"abc"}}}
However, if you do the same against a params hash with the uploaded file, you get
upload = ActionDispatch::Http::UplaodedFile.new tempfile: StringIO.new("abc"), filename: "abc"
params = ActionController::Parameters.new screenshot: { title: "afa", assets_attributes: {"0" => {filename: upload}}}
p = params.require(:screenshot).permit(:title, assets_attributes: :filename)
# => {"title" => "afa", "assets_attributes"=>{"0"=>{}}}
So, it is probably worth a bug or pull request to Rails, and in the meantime, you will have to directly access the filename parameter using the raw params
object:
params[:screenshot][:assets_attributes]["0"][:filename]
Solution 2
So, you're dealing with has_many forms and strong parameters.
This is the part of the params hash that matters:
"assets_attributes"=>{
"0"=>{
"filename"=>#<ActionDispatch::Http::UploadedFile:0x00000004edbe40
@tempfile=#<File:/tmp/RackMultipart20130123-18328-navggd>,
@original_filename="EK000005.JPG",
@content_type="image/jpeg",
@headers="Content-Disposition: form-data; name=\"screenshot[assets_attributes][0][filename]\"; filename=\"EK000005.JPG\"\r\nContent-Type: image/jpeg\r\n">
}
}
when you define strong parameters like this...
permit(:assets_attributes => [:filename])
Things break, because where rails expects a filename
it's getting this "0"
What does that number mean? It's the id
for the asset you are submitting via your form. Now initially you might think you have to do something like
permit(:assets_attributes => [:id => [:filename]])
This looks like it follows other strong parameters syntax conventions. However, for better or for worse, they have made things a little easier, and all you have to write is:
permit(:assets_attributes => [:asset_id, :filename])
Edit - As jpwynn pointed out in the comments, in Rails 4.2.4+ the correct syntax is
permit(:assets_attributes => [:id, :filename])
and that should work.
When you hit walls with strong params, the best thing to do is throw a debugger in your controller and test things out. params.require(:something).permit(:other_things)
is just a method chain so you can try out different things on the full params hash until you find what works.
Solution 3
try
def screenshot_params
params.require(:screenshot).permit(:title, :assets_attributes => [:filename, :id, :screenshot_id])
end
I had this issue about a month ago and some searching around dug up this solution. It was adding the :id or :screenshot_id that fixed the problem (or both, I can't remember). This works in my code though.
Solution 4
Actually there is a way to just white-list all nested parameters.
params.require(:screenshot).permit(:title).tap do |whitelisted|
whitelisted[:assets_attributes ] = params[:screenshot][:assets_attributes ]
end
This method has advantage over other solutions. It allows to permit deep-nested parameters.
While other solutions like:
params.require(:screenshot).permit(:title, :assets_attributes => [:filename, :id, :screenshot_id])
Don't.
Source:
https://github.com/rails/rails/issues/9454#issuecomment-14167664
John
Updated on November 03, 2020Comments
-
John over 3 years
I was playing around with Rails 4.x beta and trying to get nested attributes working with carrierwave. Not sure if what I'm doing is the right direction. After searching around, and then eventually looking at the rails source and strong parameters I found the below notes.
# Note that if you use +permit+ in a key that points to a hash, # it won't allow all the hash. You also need to specify which # attributes inside the hash should be whitelisted.
So its saying you have to specify every single every single attribute within the has, I tried the following:
Param's example:
{"utf8"=>"✓", "authenticity_token"=>"Tm54+v9DYdBtWJ7qPERWzdEBkWnDQfuAQrfT9UE8VD=", "screenshot"=>{ "title"=>"afs", "assets_attributes"=>{ "0"=>{ "filename"=>#<ActionDispatch::Http::UploadedFile:0x00000004edbe40 @tempfile=#<File:/tmp/RackMultipart20130123-18328-navggd>, @original_filename="EK000005.JPG", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"screenshot[assets_attributes][0][filename]\"; filename=\"EK000005.JPG\"\r\nContent-Type: image/jpeg\r\n"> } } }, "commit"=>"Create Screenshot"}
Controller
def screenshot_params params.require(:screenshot).permit(:title, :assets_attributes => [:filename => [:@tempfile,:@original_filename,:@content_type,:@headers]
The above isn't "working" (its not triggering carrierwave) however I am no longer getting errors (Unpermitted parameters: filename) when using the standard nested examples I found ex:
def screenshot_params params.require(:screenshot).permit(:title, assets_attributes: :filename)
If anyone could help it would be great. I was not able to find a example with nested with a key that points to a hash.
-
John over 11 yearsThanks again for your help. I will do, at least I have a hackish way of getting around it now.
-
courtsimas almost 11 yearsthis seems to still be the case (for me at least) in rails 4.0.0rc1 and it sucks.
-
ctilley79 almost 11 yearsI'm having an issue with this when using carrierwave. Any news?
-
Peter Csiba over 10 yearsthis should be the "state of the art" in CW NF: github.com/firedev/cw_nf_rails4
-
KonstantinK over 10 yearsIn the latest versions the issue described last doesn't exist anymore. For me at least uploaded files do not get thrown away. Also: beware of the typo. UplaodedFile -> UploadedFile
-
jpw over 8 yearsIn Rails 4.2.4 the latter syntax example should be permit(:assets_attributes => [:id, :filename]) not permit(:assets_attributes => [:asset_id, :filename]), eg the model name is not prefixed before 'id'
-
bwest87 about 8 yearsYeah, also, this solution is nice if you've got like 50 fields on a certain object, the way I do. Maintaining a list of attrs to whitelist that huge is pretty annoying if you don't really have major "exposure" issues to worry about.
-
jrochkind about 8 yearsThis defeats the security purpose of strong parameters entirely.