Upload multiple files with HTML5, Rails, Mongoid, Paperclip and Google Cloud Storage

It’s strongly recommended that you do some reading on Mongoid before diving into this part. This tutorial assumes you’re using Ruby 2.0.0-p0. Now let’s upload some files.

HTML 5 supports multi-file uploads right out of the box with the multiple attribute.

<input type=“file” multiple=true />

This gives us the ability to select and upload multiple files without the need for flash or javascript workarounds. Let’s look at how to integrate this into a rails application that uses Mongoid, Paperclip and Google Cloud Storage.

In your model

In Mongoid, models are actually referred to as documents, but we’re going to call them models anyway. The beauty of Mongoid and MongoDB is that we eliminate the need for database migrations. Mongoid lets us define and modify our “table” fields right inside the model.

So in image.rb we have the following:

class Image
  include Mongoid::Document
  include Mongoid::Paperclip

  field :listing_id, type: Integer
  attr_accessible :file

  embedded_in :listing, :inverse_of => :images
  has_mongoid_attached_file :file

And in listing.rb:

class Listing
  include Mongoid::Document

  field :description, type: String

  attr_accessible :description, :images_attributes

  embeds_many :images, :cascade_callbacks => true
  accepts_nested_attributes_for :images, :allow_destroy => true

Including Mongoid::Document is the equivalent of doing class Image < ActiveRecord::Base in a default rails configuration. We then include Mongoid::Paperclip.

For the sake of brevity, I will leave the explanation of Mongoid methods to some diligent research on your part. Google is your friend here. Let’s move on to the listings controller.

In your controller

Generating a scaffold on the listing model will create listings_controller.rb which contains the following create action:

def create
  @listing = Listing.new(params[:listing])
  respond_to do |format|
    if @listing.save
      # Render stuff
      # Render other stuff

Thanks to HTML5 and accepts_attributes_for in our listing model, we don’t have to modify the create action at all.

In your view file: _form.html.erb

Thus far you’ve seen HTML5 afford us a simple code base. We continue that trend but with a small caveat. Normally when you’re building forms that upload files in Rails, you do something like:

<%= form_for @listing, html: { multipart: true, class: "custom" } do |f| %>
  <%= f.file_field :image %>
<%= end %>

But we’re doing a multi-file upload and the above only supports one file at a time. As a result, we have to build the file field manually like so:

<%= file_field_tag('listing_images_attributes_file', multiple: true, name: "listing[images_attributes][][file]") %>

In simple terms, setting the name attribute to listing[images_attributes][][file] tells listings_controller.rb that we have multiple files. Since we told our listing model to accept nested attributes for the image model, the files data will pass through successfully and upload to Google Cloud Storage.

To see if it worked, check your Google Cloud Storage bucket. If it didn’t work, leave a comment and we’ll hash it out.

That’s it. Reward yourself with a beer. And if you’re stumbling upon this without having read the first part, well, go read the first part!

March 01, 2013