Pergunta

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.

Foi útil?

Solução

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]

Outras dicas

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.

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.

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

I had same problem just got it fixed now all you have to do is

params.require(:vehicle).permit(:user_id, assets_attributes: [:id, :image]).

Use pry gem to see what kind of attributes your asset object has makes sure theres an id and add other missing attribute, that should then work perfectly. Am using paperclip assets is my nested object inside the vehicle class and an attachment of images is added to the asset. make sure you do the validation in the model

accepts_nested_attributes_for :assets, allow_destroy: true
validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/

In your view loop through the asset to get each image

<%= @vehicle.assets.size %>
    <% for asset in @vehicle.assets %>
        <%=link_to image_tag (asset.image.url(:thumb)) %>
    <% end %>

If am correct your problem is that asset_attributes is an array with each image having an index column and an image

Your form_for should have something similar to this and if you want you can also include preview so the upload can view their images use the bottom code for that

<div class="field">
    <h3>Vehicle Image Upload</h3>
    <%= f.fields_for :assets do |asset_fields| %>

        <% if asset_fields.object.new_record? %>
            <p>
                <%= asset_fields.file_field :image %>
            </p>
        <% end %>
    <% end %>
</div>

<div class="field">
    <h4>Vehicle Image</h4>
    <%= f.fields_for :assets do |asset_fields| %>

        <% unless asset_fields.object.new_record? %>
          <%= link_to image_tag(asset_fields.object.image.url(:thumb)),
                    asset_fields.object.image.url(:original)%>
          <%= asset_fields.check_box :_destroy %>
        <% end %>
    <% end %>
</div>

Sanitize before save in controller Sanitize accepts_nested_attributes_for attributes with index.

before_action :sanitize_fields_params, :only => [:create, :update]

def sanitize_fields_params

    product_free_shippings_attributes = params[:product][:product_free_shippings_attributes]

    product_free_shippings_attributes.each do |index, key_value|
      params[:product][:product_free_shippings_attributes]["#{index}"][:weight] = clear_decimal(key_value[:weight])
      params[:product][:product_free_shippings_attributes]["#{index}"][:height] = clear_decimal(key_value[:height])
      params[:product][:product_free_shippings_attributes]["#{index}"][:width] = clear_decimal(key_value[:width])
      params[:product][:product_free_shippings_attributes]["#{index}"][:depth] = clear_decimal(key_value[:depth])
    end
 end

 def clear_decimal(field) 
    return (field.to_s.gsub(/[^\d]/, '').to_d / 100.to_d) unless field.blank?
  end
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top