5 Comments

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.

1
2
3
4
5
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.

1
2
3
4
5
6
7
8
9
10
11
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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.

1
2
3
4
def create
  uploaded_file = UploadedFile.new params[:file_name]
  @thing = Thing.create!(:attachment => uploaded_file)
end