4
votes

I'm aware that there are several posts on Stackoverflow and several tutorials about this subject. None of them however manage to solve my issue and most of them are outdated as well.

I am trying to add multiple images to a project using the paperclip gem in Rails 4. When i try uploading it i do see the asset attached in the params.

They do not seem to be added to the project_paramns though..

Hope someone can help me out here.

This is my projects_controller

class ProjectsController < ApplicationController

  before_filter :find_project, only: [:show, :edit, :update, :destroy]

  def index
   @projects = Project.all
  end

  def show
  end

  def new
    @project = Project.new
  end

  def create
    @project = Project.new(project_params)
    @project.save

    redirect_to project_path(@project)
  end

  def edit
  end

  def update
    @project.update(project_params)

    redirect_to project_path(@project)
  end

  def destroy
    @project.destroy

    redirect_to projects_path
  end

  protected

    def project_params
      params.require(:project).permit(:name, :description, :asset)
    end

    def find_project
      @project = Project.find(params[:id])
    end
end

My project model

class Project < ActiveRecord::Base
  has_many :assets, :dependent => :destroy

  validates_associated :assets
  validates_presence_of :name, :on => :create, :update => "can't be blank"
  validates_presence_of :description, :on => :create, :update => "can't be blank"

  accepts_nested_attributes_for :assets
end

My asset model

class Asset < ActiveRecord::Base
    belongs_to :project

  # Paperclip
  has_attached_file :image,
    :styles => {
      :thumb=> "100x100#",
      :small  => "150x150>",
      :medium => "300x300>",
      :large =>   "400x400>" }
  validates_attachment :image, content_type: { content_type: ["image/jpg", "image/jpeg",     "image/png", "image/gif"] }
end

And my form partial (Sorry it's in HAML)

= simple_form_for @project do |f|
  %ul
    - @project.errors.full_messages.each do |error|
      %li= error
  .row
    .small-1.columns    
      = f.label :name, :class => "left inline"
    .small-11.columns
      = f.input_field :name
  .row
    .small-1.columns
      = f.label :description, :class => "left inline"
    .small-11.columns 
      = f.input_field :description, as: :text
  .row
    = f.simple_fields_for :asset do |a|
      .small-1.columns
        = a.label :image, :class => "left inline"
      .small-11.columns
        = file_field_tag :image, multiple: true,
  .row
    .small-9.small-offset-1.columns
      = f.submit nil ,:class => "button [radius round]"

Request Parameters

{"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"4iK1kUNvKvoJVOKoivz/pcLAe6LY0cUJikQioxa8BIs=", "project"=>{"name"=>"adf", "description"=>"adf", "asset"=>{"image"=>[#<ActionDispatch::Http::UploadedFile:0x007fb808357d80 @tempfile=#<Tempfile:/var/folders/v_/98sxm8bn24qbqj_jmj40fv400000gn/T/RackMultipart20140607-34739-dvlzt7>, @original_filename="enabling-gzip-compression.jpg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"project[asset][image][]\"; filename=\"enabling-gzip-compression.jpg\"\r\nContent-Type: image/jpeg\r\n">, #<ActionDispatch::Http::UploadedFile:0x007fb808357d58 @tempfile=#<Tempfile:/var/folders/v_/98sxm8bn24qbqj_jmj40fv400000gn/T/RackMultipart20140607-34739-lwkioi>, @original_filename="minimize_http_requests.png", @content_type="image/png", @headers="Content-Disposition: form-data; name=\"project[asset][image][]\"; filename=\"minimize_http_requests.png\"\r\nContent-Type: image/png\r\n">]}}, "commit"=>"Update Project", "action"=>"update", "controller"=>"projects", "id"=>"11"}

Now i've also got an error showing up in my terminal:

Unpermitted parameters: asset

Edit:

A combination of @pavan's and @kiri thorat's answers have helped me get something showing up in project_params, the output it gives now is:

{"name"=>"Test", "description"=>"Test", "assets_attributes"=>{"0"=>{}}}

Any clue on what's going on here?

After @kirithorat's latest update things seem to be good on the parameter side of things.

{"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"4iK1kUNvKvoJVOKoivz/pcLAe6LY0cUJikQioxa8BIs=", "project"=>{"name"=>"Test", "description"=>"Test", "assets_attributes"=>{"0"=>{"image"=>#<ActionDispatch::Http::UploadedFile:0x007fcd383c9a08 @tempfile=#<Tempfile:/var/folders/v_/98sxm8bn24qbqj_jmj40fv400000gn/T/RackMultipart20140610-36517-7ek1oq>, @original_filename="enabling-gzip-compression.jpg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"project[assets_attributes][0][image]\"; filename=\"enabling-gzip-compression.jpg\"\r\nContent-Type: image/jpeg\r\n">}}}, "commit"=>"Update Project", "action"=>"update", "controller"=>"projects", "id"=>"13"}

The assets are still not being saved though.

Update after implementing @Valikiliy's suggestions

{"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"4iK1kUNvKvoJVOKoivz/pcLAe6LY0cUJikQioxa8BIs=", "project"=>{"name"=>"Test", "description"=>"Test", "image"=>#<ActionDispatch::Http::UploadedFile:0x007fcd3a08d0b8 @tempfile=#<Tempfile:/var/folders/v_/98sxm8bn24qbqj_jmj40fv400000gn/T/RackMultipart20140610-36517-rgy95n>, @original_filename="minimize_http_requests.png", @content_type="image/png", @headers="Content-Disposition: form-data; name=\"project[image]\"; filename=\"minimize_http_requests.png\"\r\nContent-Type: image/png\r\n">}, "commit"=>"Update Project", "action"=>"update", "controller"=>"projects", "id"=>"16"}

Update: Added new form code on request

= simple_form_for(@project, :html => { :multipart => true }) do |f|
  %ul
    - @project.errors.full_messages.each do |error|
      %li= error
  .row
    .small-1.columns    
      = f.label :name, :class => "left inline"
    .small-11.columns
      = f.text_field :name
  .row
    .small-1.columns
      = f.label :description, :class => "left inline"
    .small-11.columns 
      = f.text_area :description
  .row
    = f.simple_fields_for :assets do |a|
      .small-1.columns
        = a.label :image, :class => "left inline"
      .small-11.columns
        - if a.object.new_record?
          = a.input :image, as: :file
        - else
          = image_tag a.object.image.url(:thumb)
          = a.input_field '_destroy', as: :boolean
  .row
    .small-9.small-offset-1.columns
      = f.submit nil ,:class => "button [radius round]"

Update

def project_params
  params.require(:project).permit(:name, :description, images: [], assets_attributes: [:_destroy, :id, :image])
end

def find_project
  @project = Project.find(params[:id])
  @project.assets.build if %w[new edit].include?(action_name) 
end

Update: Added model code

class Project < ActiveRecord::Base
  has_many :assets, :dependent => :destroy

  validates_presence_of :name, :on => :create, :update => "can't be blank"
  validates_presence_of :description, :on => :create, :update => "can't be blank"

  accepts_nested_attributes_for :assets, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
end
3

3 Answers

17
votes

To save multiple files in the above example you, do not need add multiple: true option, it will cause an error when saving, one file - one record assets table.

For resolve this, you can add multiple file fields, like this: add into controller:

def new
  @project = Project.new
  3.times{ @project.assets.build } 
end

def edit
  3.times{ @project.assets.build } 
end

for while list

.permit(..., assets_attributes: [ :_destroy, :id, :image ])

and in view like this:

= f.simple_fields_for :assets do |a|
  .small-1.columns
    = a.label :image, :class => "left inline"
    - if a.object.new_record?
      .small-11.columns
        = a.file_field :image
    - else
      .small-11.columns
        = image_tag a.object.image.url(:thumb)
        = a.input_field '_destroy', as: :boolean

If you try to send multiple images like this:

# view
a.file_field :image, multiple: true

# controller
.permit(..., assets_attributes: [ :_destroy, :id, image: [] ])

This will cause an error because the Asset model does not know what to do with an array of images.

To save more than one file at the same time, you need to use your own handler, for example:

model: add the images= method

def images=(files = [])
  assets.create(image: f)
end

controller: move images: [] outside of the assets_attributes

.permit(..., images: [], assets_attributes: [ :_destroy, :id ])

view: remove the fields_for and nested attributes

  .row
    .small-1.columns
      =f.file_field :images, multiple: true
  .row
    = f.simple_fields_for :assets do |a|
      .small-1.columns
        = a.label :image, :class => "left inline"
....
4
votes

You have 1-M relationship between Project and Asset models i.e.,

Project has_many assets

so, in your form partial simple_fields_for should look like

= f.simple_fields_for :assets do |a| 

Notice assets in plural and NOT asset in singular

In your current code, you used simple_fields_for with singular asset which is why your params is generated incorrectly and you receive asset key in params hash instead of receiving assets_attributes key which results in the warning as Unpermitted parameters: asset.

Once you correct the form partial you would receive the correct keys in params hash upon form submission. Now, as @Pavan pointed out next problem that I see is you have not permitted the assets_attributes correctly in the controller.

You need to update the project_params method as below:

def project_params
  params.require(:project).permit(:name, :description, assets_attributes: [:image])
end

Notice assets_attributes with assets in plural

UPDATE

You would need to add @project.assets.build in new and edit action of ProjectsController in order to see the fields for assets in the new and edit view. Also, I would suggest adding :id in the list of permitted attributes for assets_attributes in project_params as below:

  params.require(:project).permit(:name, :description, assets_attributes: [:id, :image])

Few more problems that I see is in the form partial are

  1. As you are uploading a file, you should specify :html => {:multipart => true} in the form.

    Change

    = simple_form_for @project do |f|
    

    To

    = simple_form_for @project, :html => {:multipart => true} do |f|
    
  2. Project has_many assets and every asset record would have only one image, so remove multiple: true. Also, as you are using simple_form, its advisable to use simple_form helper method for uploading file.

    Change

    = file_field_tag :image, multiple: true,
    

    To

    = a.input :image, as: :file
    
3
votes

Your project_params should be like this

def project_params
  params.require(:project).permit(:name, :description, assets_attributes: [:image])
end

And also,why you are using protected? i guess you should be using private.