Rails 4.0 Strong Parameters nested attributes with a key that points to a hash

43,572

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

Share:
43,572
John
Author by

John

Updated on November 03, 2020

Comments

  • John
    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.
    

    https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/strong_parameters.rb

    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
    John over 11 years
    Thanks again for your help. I will do, at least I have a hackish way of getting around it now.
  • courtsimas
    courtsimas almost 11 years
    this seems to still be the case (for me at least) in rails 4.0.0rc1 and it sucks.
  • ctilley79
    ctilley79 almost 11 years
    I'm having an issue with this when using carrierwave. Any news?
  • Peter Csiba
    Peter Csiba over 10 years
    this should be the "state of the art" in CW NF: github.com/firedev/cw_nf_rails4
  • KonstantinK
    KonstantinK over 10 years
    In 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
    jpw over 8 years
    In 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
    bwest87 about 8 years
    Yeah, 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
    jrochkind about 8 years
    This defeats the security purpose of strong parameters entirely.