Home

Awesome

S3DirectUpload

Join the chat at https://gitter.im/waynehoover/s3_direct_upload

Build Status

Easily upload files directly to Amazon S3. Multi file uploading supported by jquery-fileupload.

Code extracted from Ryan Bates' gallery-jquery-fileupload.

Installation

Add this line to your application's Gemfile:

gem 's3_direct_upload'

Then add a new initalizer with your AWS credentials:

config/initializers/s3_direct_upload.rb

S3DirectUpload.config do |c|
  c.access_key_id = ""       # your access key id
  c.secret_access_key = ""   # your secret access key
  c.bucket = ""              # your bucket name
  c.region = nil             # region prefix of your bucket url. This is _required_ for the non-default AWS region, eg. "s3-eu-west-1"
  c.url = nil                # S3 API endpoint (optional), eg. "https://#{c.bucket}.s3.amazonaws.com/"
end

Make sure your AWS S3 CORS settings for your bucket look something like this:

<CORSConfiguration>
    <CORSRule>
        <AllowedOrigin>http://0.0.0.0:3000</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>POST</AllowedMethod>
        <AllowedMethod>PUT</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

In production the AllowedOrigin key should be your domain.

Add the following js and css to your asset pipeline:

application.js.coffee

#= require s3_direct_upload

application.css

//= require s3_direct_upload_progress_bars

Usage

Use the s3_uploader_form helper to add an s3 upload file field to your view:

<%= s3_uploader_form callback_url: model_url, callback_param: "model[image_url]", id: "s3-uploader" do %>
  <%= file_field_tag :file, multiple: true, data: { url: s3_uploader_url } %>
<% end %>

Then in your application.js.coffee, call the S3Uploader jQuery plugin on the element you created above:

jQuery ->
  $("#s3-uploader").S3Uploader()

Optionally, you can also place this template in the same view for the progress bars:

<script id="template-upload" type="text/x-tmpl">
<div id="file-{%=o.unique_id%}" class="upload">
  {%=o.name%}
  <div class="progress"><div class="bar" style="width: 0%"></div></div>
</div>
</script>

Options for form helper

Example with all options

<%= s3_uploader_form callback_url: model_url,
                     callback_method: "POST",
                     callback_param: "model[image_url]",
                     key: "files/{timestamp}-{unique_id}-#{SecureRandom.hex}/${filename}",
                     key_starts_with: "files/",
                     acl: "public-read",
                     max_file_size: 50.megabytes,
                     id: "s3-uploader",
                     class: "upload-form",
                     data: {:key => :val} do %>
  <%= file_field_tag :file, multiple: true, data: { url: s3_uploader_url } %>
<% end %>

Example to persist the S3 url in your rails app

It is recommended that you persist the url that is sent via the POST request (to the url given to the callback_url option and as the key given in the callback_param option).

One way to do this is to make sure you have resources model in your routes file, and add a s3_url (or something similar) attribute to your model. Then make sure you have the create action in your controller for that model that saves the url from the callback_param.

You could then have your create action render a javascript file like this: create.js.erb

<% if @model.new_record? %>
  alert("Failed to upload model: <%= j @model.errors.full_messages.join(', ').html_safe %>");
<% else %>
  $("#container").append("<%= j render(@model) %>");
<% end %>

So that javascript code would be executed after the model instance is created, without a page refresh. See @rbates's gallery-jquery-fileupload) for an example of that method.

Note: the POST request to the rails app also includes the following parameters filesize, filetype, filename and filepath.

Advanced Customizations

Feel free to override the styling for the progress bars in s3_direct_upload_progress_bars.css, look at the source for inspiration.

Also feel free to write your own js to interface with jquery-file-upload. You might want to do this to do custom validations on the files before it is sent to S3 for example. To do this remove s3_direct_upload from your application.js and include the necessary jquery-file-upload scripts in your asset pipeline (they are included in this gem automatically):

#= require jquery-fileupload/basic
#= require jquery-fileupload/vendor/tmpl

Use the javascript in s3_direct_upload as a guide.

Options for S3Upload jQuery Plugin

Example with all options

jQuery ->
  $("#myS3Uploader").S3Uploader
    path: 'path/to/my/files/on/s3'
    additional_data: {key: 'value'}
    remove_completed_progress_bar: false
    before_add: myCallBackFunction # must return true or false if set
    progress_bar_target: $('.js-progress-bars')
    click_submit_target: $('.submit-target')

Example with single file upload bar without script template

This demonstrates how to use progress_bar_target and allow_multiple_files (only works with false option - single file) to show only one progress bar without script template.

jQuery ->
  $("#myS3Uploader").S3Uploader
    progress_bar_target: $('.js-progress-bars')
    allow_multiple_files: false

Target for progress bar

<div class="upload js-progress-bars">
  <div class="progress">
    <div class="bar"> </div>
  </div>
</div>

Public methods

You can change the settings on your form later on by accessing the jQuery instance:

jQuery ->
  v = $("#myS3Uploader").S3Uploader()
  ...
  v.path("new/path/") #only works when the key_starts_with option is blank. Not recommended.
  v.additional_data("newdata")

Javascript Events Hooks

First upload started

s3_uploads_start is fired once when any batch of uploads is starting.

$('#myS3Uploader').bind 's3_uploads_start', (e) ->
  alert("Uploads have started")

Successful upload

When a file has been successfully uploaded to S3, the s3_upload_complete is triggered on the form. A content object is passed along with the following attributes :

This hook could be used for example to fill a form hidden field with the returned S3 url :

$('#myS3Uploader').bind "s3_upload_complete", (e, content) ->
  $('#someHiddenField').val(content.url)

Failed upload

When an error occured during the transferm the s3_upload_failed is triggered on the form with the same content object is passed for the successful upload with the addition of the error_thrown attribute. The most basic way to handle this error would be to display an alert message to the user in case the upload fails :

$('#myS3Uploader').bind "s3_upload_failed", (e, content) ->
  alert("#{content.filename} failed to upload : #{content.error_thrown}")

All uploads completed

When all uploads finish in a batch an s3_uploads_complete event will be triggered on document, so you could do something like:

$(document).bind 's3_uploads_complete', ->
    alert("All Uploads completed")

Rails AJAX Callbacks

In addition, the regular rails ajax callbacks will trigger on the form with regards to the POST to the server.

$('#myS3Uploader').bind "ajax:success", (e, data) ->
  alert("server was notified of new file on S3; responded with '#{data}")

Cleaning old uploads on S3

You may be processing the files upon upload and reuploading them to another bucket or directory. If so you can remove the originali files by running a rake task.

First, add the fog gem to your Gemfile and run bundle:

  gem 'fog'

Then, run the rake task to delete uploads older than 2 days:

  $ rake s3_direct_upload:clean_remote_uploads
  Deleted file with key: "uploads/20121210T2139Z_03846cb0329b6a8eba481ec689135701/06 - PCR_RYA014-25.jpg"
  Deleted file with key: "uploads/20121210T2139Z_03846cb0329b6a8eba481ec689135701/05 - PCR_RYA014-24.jpg"
  $

Optionally customize the prefix used for cleaning (default is uploads/#{2.days.ago.strftime('%Y%m%d')}): config/initalizers/s3_direct_upload.rb

S3DirectUpload.config do |c|
  # ...
  c.prefix_to_clean = "my_path/#{1.week.ago.strftime('%y%m%d')}"
end

Alternately, if you'd prefer for S3 to delete your old uploads automatically, you can do so by setting your bucket's Lifecycle Configuration.

A note on IE support

IE file uploads are working but with a couple caveats.

But IE should still upload your files fine.

Contributing / TODO

This is just a simple gem that only really provides some javascript and a form helper. This gem could go all sorts of ways based on what people want and how people contribute. Ideas:

Credit

This gem is basically a small wrapper around code that Ryan Bates wrote for Railscast#383. Most of the code in this gem was extracted from gallery-jquery-fileupload.

Thank you Ryan Bates!

This code also uses the excellent jQuery-File-Upload, which is included in this gem by its rails counterpart jquery-fileupload-rails