Multiple Files Upload With Nested Resource Using Paperclip in Rails

Recipe #6 | posted in Controller, View | Comments

The structure of our app is the following: A Gallery has many Pictures. Each Picture has one image, which is the paperclip attachment.

Gallery

Lets start with the Gallery model

#app/models/gallery.rb
1
2
3
4
5
class Gallery < ActiveRecord::Base
  attr_accessible :description, :name, :pictures

  has_many :pictures, :dependent => :destroy
end

In app/views/galleries/_form.html.erb we set up the form to create a new gallery. Use HTML5 multiple attribute to enable multiple file selection. On submit all selected files are returned to the gallery controller as an array "images[]", ready for paperclip!!

file_field_tag is used since images is not a @gallery attribute.

#app/views/galleries/_form.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<%= form_for @gallery, :html => { :class => 'form-horizontal', multipart: true } do |f| %>
  <div class="control-group">
    <%= f.label :name, :class => 'control-label' %>
    <div class="controls">
      <%= f.text_field :name, :class => 'text_field' %>
    </div>
  </div>
  <div class="control-group">
    <%= f.label :description, :class => 'control-label' %>
    <div class="controls">
      <%= f.text_field :description, :class => 'text_field' %>
    </div>
  </div>

  <div class="control-group">
    <%= f.label :pictures, :class => 'control-label' %>
    <div class="controls">
      <!-- The magic is coming ...look at my eyes....shazammmm -->
      <!-- Use HTML5 multiple attribute to enable multiple selection
           and pass back to controller all files as an array, ready 
           for paperclip!!
           file_field_tag, since images is not a gallery attribute
       -->
      <%= file_field_tag "images[]", type: :file, multiple: true %>
    </div>
  </div>

  <div class="form-actions">
    <%= f.submit nil, :class => 'btn btn-primary' %>
    <%= link_to t('.cancel', :default => t("helpers.links.cancel")),
                galleries_path, :class => 'btn btn-mini' %>
  </div>
<% end %>

Line 24 shows the magic :P

1
<%= file_field_tag "images[]", type: :file, multiple: true %>

In app/controllers/galleries_controller.rb#create, gallery is saved first and then all images. Thus, there is no need to keep any unnecessary tokens, hashes…etc, just to give a gallery_id in pictures new entries.

#app/controllers/galleries_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def create
  @gallery = Gallery.new(params[:gallery])

  respond_to do |format|
    if @gallery.save

      if params[:images]
        #===== The magic is here ;)
        params[:images].each { |image|
          @gallery.pictures.create(image: image)
        }
      end

      format.html { redirect_to @gallery, notice: 'Gallery was successfully created.' }
      format.json { render json: @gallery, status: :created, location: @gallery }
    else
      format.html { render action: "new" }
      format.json { render json: @gallery.errors, status: :unprocessable_entity }
    end
  end
end

Picture

Just for the record, below you can check the Picture model.

#app/models/picture.rb
1
2
3
4
5
6
7
8
9
10
11
class Picture < ActiveRecord::Base
  attr_accessible :description, :gallery_id, :image

  belongs_to :gallery

  has_attached_file :image,
    :path => ":rails_root/public/images/:id/:filename",
    :url  => "/images/:id/:filename"

  do_not_validate_attachment_file_type :image
end

Demo app

Clone the demo application if you want to play with the source. In a following post, I will try to add validation and preview of the files before upload, like jquery-file-upload plugin.

Comments