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

“Plupload”:http://www.plupload.com/ is an open source javascript upload handler that supports uploading files directly to “Amazon S3”:http://aws.amazon.com/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”:https://github.com/iwasrobbed/Rails3-S3-Uploader-Plupload 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”:https://github.com/iwasrobbed/Rails3-S3-Uploader-Plupload/blob/master/app/helpers/uploads_helper.rb file where the bulk of the Plupload code is located. Also be sure to upload a publicly readable “crossdomain.xml”:https://gist.github.com/995182 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.

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. I’m 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
Conversation
  • Simon says:

    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

    • 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.

  • John Sawers says:

    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.

  • Comments are closed.