Drag & Drop upload with Rails, Rack Raw Upload & File Uploader

August 17, 2011

The idea behind this post is to show you how to integrate the following:

I’m going to assume you have a working upload form and show you how to modify your Rails application to support the new feature. In my case, I had a very simple form to upload documents and it got tiring when there were lots to upload. I will assume that your model is called article and that you are saving files by writing to @article.file.

You may also be interested in reading this post.

File Uploader

The first think you want to do is grab a copy of file-uploader. You can do this however way you want. In my case, I added a submodule to my app so that I could sync any future updates over with ease. Example:

mkdir -p vendor/misc
git submodule add git://github.com/valums/file-uploader.git vendor/misc/file-uploader
cp vendor/misc/file-uploader/client/fileuploader.js public/javascripts

# Optional
cp vendor/misc/file-uploader/client/fileuploader.css public/stylesheets
cp vendor/misc/file-uploader/client/loading.gif public/images

At this point you’ll want to fiddle with fileuploader.css and correct the link to loading.gif (should be /images/loading.gif). Or you can use your own.

Rack Raw Upload

After adding gem "rack-raw-upload" to your Gemfile (and bundle), you’ll need to add and configure the middleware, like so:

require 'rack/raw_upload'
config.middleware.use 'Rack::RawUpload', :paths => ['/articles']

At this point, restart your application and the middleware will intercept POST /articles, which is what happens when you have a <%= form_for(@article) .. %>, and the article is new.


Assuming we have a very simple form which looks something like (app/views/articles/new.html.erb):

<%= form_for(@article, :html => { :multipart => true }) do |f| %>
  <p><%= f.select :category, Article.categories %></p>
  <p><%= f.file_field :file %></p>
  <%= f.submit("Upload") %>
<% end %>

What we’ll do is make this form support both “legacy” and “drag & drop”. The following simple change will do.

<%= form_for(@article, :html => { :multipart => true }) do |f| %>
  <p><%= f.select :category, Article.categories %></p>
  <h3>Legacy uploader</h3>
  <p><%= f.file_field :file %></p>
  <%= f.submit("Upload") %>
  <h3>Or drop files here</h3> 
  <div id="file-uploader"></div>
<% end %>

Very simple. All we’ve done is add an empty div with id file-uploader, plus added headings so people can continue to use the “legacy” method. This could be hidden with noscript if you so desire.

Ensure you have a yield :head inside the <head> of your layout. Now add the following to app/views/articles/new.html.erb:

<%= content_for :head do %>
  <%= javascript_include_tag "fileuploader" %>
  <%= javascript_include_tag "article_drag_drop" %>
  <%= stylesheet_link_tag "fileuploader" %>
<% end %>

Now we need to create javascripts/article_drag_drop. Note that my implementation uses jQuery, but this is definitely not required and file-uploader doesn’t require it.

$(function() {  
  var uploader = new qq.FileUploader({
    debug: false,
    /* Do not use the jQuery selector here */
    element: document.getElementById("file-uploader"),
    action: $("#new_article").attr("action"),
    allowedExtensions: ["txt"],
     * This uploads via browser memory. 1 MB example.
    sizeLimit: 1048576,

    /* Set Article category on submit */
    onSubmit: function(id, fileName) {
        authenticity_token: $("input[name='authenticity_token']").attr("value"),
        article: {
          category: $("#article_category :selected").text(),


There are a couple of things to note here:

Finally, you’ll need to tweak your controller a little bit. In app/views/controllers/articles_controller.rb:

  def create
    is_qq = params.has_key?(:qqfile)
    if is_qq
      params[:article][:file] = params.delete(:file)

    # .. elided ..
    if @article.save
      if is_qq
        render :json => { "success" => true }
        # as before, likely:
        # redirect_to(articles_path, :notice => "Article was successfully created.")
      if is_qq
        render :json => { "error" => @article.errors }
        # as before, likely:
        # render :action => :new

This should be fairly obvious. When the POST is made, a special qqfile parameter is added to the query string. This parameter contains the filename. What we want to do is make it behave like legacy mode. Thus, we move :file into params[:article] if we’re in ‘qq-mode’. Similarly, after we create our article, we return some json. Note that file-uploader will eval this. If it responds to success, it will think all went well. Else it’ll show that the upload failed. You can enable debug (by setting debug: true in the Javascript) to see the contents of your error. It may be useful to return @article.errors.inspect in the case of an error to make it easier to read in the console.