We're hiring!

We're actively seeking developers for our new Detroit location. Learn more

Upload Files Directly to S3 with Plupload, Rails, and Paperclip

Plupload is an open source javascript upload handler that supports uploading files directly to Amazon S3. This is an alternative to uploading files to the web server, and then to S3. You will need to use the Flash or Silverlight options to upload directly to S3 because Amazon has yet to enable cross-origin uploading.

In researching how to use Plupload with Rails, I came across this excellent Rails3-S3-Uploader-Pluplod sample Rails 3 application. It very nicely lays out what needs to be done to combine Plupload, Rails, and direct S3 uploads. You should pay particular attention to the uploads_helper.rb file where the bulk of the Plupload code is located. Also be sure to upload a publicly readable crossdomain.xml to the root directory of your S3 bucket to give Flash access to the bucket.

In my implementation I chose to break the javascript out into a separate file, instead of it being generated in a helper. I also added an event handler for the FileUploaded event so the page could react to the file upload completion.

uploader.bind('FileUploaded', function(up, file) {
  // Function that POSTs an ajax request to the server
  // with the name of the new file
  fileUploaded(file.name);
});

The Rails application will receive the POST indicating that the image has been uploaded to S3. The controller then needs to instantiate a Paperclip model. An example of setting up a Paperclip model using the S3 storage option can be seen in Dogan Kaya Berktas’ post Amazon S3 and Paperclip on Rails 3.

The trick here is that unlike with a normal file upload, we don’t have a temp file on the local web server – instead it is sitting in an S3 bucket. There are several ways of handling this. One is described in this Rails Toolkit post. I chose to go a different route; I am instead instantiating a custom wrapper and passing that into Paperclip.

class Thing < ActiveRecord::Base
 
  has_attached_file :attachment,
    :path => "/uploads/:style/:basename.:extension",
    :storage => :s3,
    :s3_credentials => "#{Rails.root}/config/s3.yml",
    :styles => {
      :thumb => "180x180#",  # Square thumbnails
      :original => "405",    # Resize to a max width for originals
    }
end
class UploadedFile
  def initialize(file_name)
    @file_name = file_name
    encoded_filename = CGI::escape(file_name)
    @io = open("http://s3.amazonaws.com/my_bucket/uploads/#{encoded_filename}")
  end
 
  def method_missing(method_name, *args)
    @io.send method_name, *args
  end
 
  def respond_to?(method)
    super || @io.respond_to?(method)
  end
 
  def original_filename
    @file_name
  end
 
  def content_type
    @content_type ||= MIME::Types.type_for(@file_name)
  end
end

In a controller (or in a background job) you can then create the Thing that has the attachment, and Paperclip will download the file from S3 before doing its processing and then uploading the processed images back to S3.

def create
  uploaded_file = UploadedFile.new params[:file_name]
  @thing = Thing.create!(:attachment => uploaded_file)
end
Patrick Bacon (44 Posts)

I’m a software developer at Atomic Object (since 2005) in Grand Rapids, MI.

This entry was posted in Web Apps and tagged , , , , , . Bookmark the permalink. Both comments and trackbacks are currently closed.

5 Comments

  1. Simon
    Posted June 21, 2012 at 6:43 am

    Hi Patrick,

    Thank you for this work through, its given me some thoughts into managing file processing post upload. I was wondering if you had a working example project of the above somewhere as I am trying to put 2 and 2 together and am struggling a little?

    Cheers,

    Simon

    • Posted July 5, 2012 at 4:52 pm

      Simon,

      Unfortunately the project I was working on is not open source, and I don’t currently have any kind of an example project that goes along with this post.

      If you have any specific questions I might be able to help you out.

  2. Posted January 6, 2013 at 10:08 pm

    It took me a while to discover, but in Ruby 1.9.x you must include require 'open-uri' in the UploadedFile class or the call to open() will fail.

    • Posted January 6, 2013 at 10:09 pm

      Also, thank you Patrick for this blog post, it was incredibly helpful

    • Posted January 7, 2013 at 8:15 am

      John, thanks for the 1.9 tip. Much appreciated.