Albert Agram

Uploading Multiple Files With Carrierwave Using a Nested Form

I recently needed to create a form for uploading multiple files to a given object. I implemented this with carrierwave and jQuery File Upload gems.

Before we start, we need to make sure models and migrations are setup. For images, we have the following migration:

class CreateImages < ActiveRecord::Migration
  def change
    create_table :images do |t|
    t.string :image
    t.integer :fraud_id

    t.timestamps
  end
end

I have the following classes:

class Fraud < ActiveRecord::Base
  has_many :images
  accepts_nested_attributes_for :images
end

class Image < ActiveRecord::Base
  belongs_to :fraud
end

Added gems to Gemfile:

gem 'carrierwave'
gem 'jquery-fileupload-rails'

Run bundle install to install gems and dependencies.

Then we create a carrierwave uploader with rails generate uploader image and mount the uploader:

class Image < ActiveRecord::Base
  belongs_to :fraud

  mount_uploader :image, ImageUploader
end

The carrierwave uploader helps by doing all the heavy lifting with regard to uploading the files.

Now we are ready to work on the view, where the form is modified in the following way:

= render 'shared/errors', object: @fraud

= form_for @fraud, html: { class: "form-horizontal", autocomplete: "off", multipart: true } do |f|
  %fieldset
    .control-group
      = f.label :title, class: 'control-label'
      .controls
        = f.text_field :title, class: 'span3'
    .control-group
      = f.label :description, class: 'control-label'
      .controls
        = f.text_area :description, rows: 6, class: 'span6'
    .control-group
      = f.label :fraud_date, class: 'control-label'
      .controls
        = f.text_field :fraud_date, class: 'span3 fraud_date'
    .control-group
      = f.fields_for :images, Image.new do |ff|
        = ff.label :image, "Upload Evidence", class: 'control-label'
        .controls
          = ff.file_field :image, multiple: true, class: 'btn btn-file', id: 'upload-image', name: "fraud[images_attributes][][image]"
  %fieldset.actions.control-group
    .controls
      = f.submit class: 'btn', id: 'submit-data'

The form setup is a typical form with a few details: = f.fields_for :images, Image.new do |ff| creates a nested form. accepts_nested_attributes_for allows the creation of a related object along with the main object. There is good documentation of this here. So in this example, we are creating an instance of Fraud class while simultaneously creating an instance of an Image class.

Since by default, most browsers do not allow selection of multiple files, we use multiple: true to change this. Also, we have to change the name attribute of our nested form to make it work with carrierwave. The attribute finally looks like this:

name: "fraud[images_attributes][][image]"

Lastly, we need our images to be uploaded on submit. jQuery File Upload gem by default tries to upload files immediately after its attached, we need to change this.

We grab the id's of the file field and the submit button and use coffeescript to change this default behaviour to fit our needs:

$ ->
  $("#upload-image").fileupload
  add: (e, data) ->
    data.context = $("#submit-data")
    data.submit()

That's all folks! With this, on submit, Rails knows to create an instance of Fraud and instances of Image. There is a natural association with Fraud and Image.