Direct to S3 upload using aws-sdk gem and jQuery-File-Upload on heroku


#1

I’m trying to achieve direct to Amazon S3 upload in Rails using
jQuery-File-Upload https://github.com/blueimp/jQuery-File-Upload and
the
aws-sdk https://github.com/aws/aws-sdk-ruby gem, and following
heroku’s
direct to S3
https://devcenter.heroku.com/articles/direct-to-s3-image-uploads-in-rails
instructions.
This is the upload form produced in the html:

<form id="pic-upload"
class="directUpload"
data-form-data="{
"key":"uploads/59c99e44-6bf2-4937-9680-02c839244b33/${filename}",
"success_action_status":"201",
"acl":"public-read",
"policy":"eyJle...In1dfQ==",
"x-amz-credential":"AKIAJCOB5HQVW5IUPYGQ/20160101/us-east-1/s3/

aws4_request",
“x-amz-algorithm”:“AWS4-HMAC-SHA256”,
“x-amz-date”:“20160101T010335Z”,
“x-amz-signature”:“0f32ae…238e”}"
data-url=“https://websmash.s3.amazonaws.com
data-host=“websmash.s3.amazonaws.com
enctype=“multipart/form-data”
action="/users/bazley/update_pictures"
accept-charset=“UTF-8”
method=“post”>

This is the corresponding jQuery:

$(function() {
  $('.directUpload').find("input:file").each(function(i, elem) {
    var fileInput    = $(elem);
    var form         = $(fileInput.parents('form:first'));
    var submitButton = form.find('input[type="submit"]');
    var progressBar  = $("<div class='bar'></div>");
    var barContainer = $("<div class='progress'></div>").append(

progressBar);
fileInput.after(barContainer);
fileInput.fileupload({
fileInput: fileInput,
url: form.data(‘url’),
type: ‘POST’,
autoUpload: true,
formData: form.data(‘form-data’),
paramName: ‘file’, // S3 does not like nested name
fields
i.e. name=“user[avatar_url]”
dataType: ‘XML’, // S3 returns XML if
success_action_status is set to 201
replaceFileInput: false,
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
progressBar.css(‘width’, progress + ‘%’)
},
start: function (e) {
submitButton.prop(‘disabled’, true);
progressBar.
css(‘background’, ‘green’).
css(‘display’, ‘block’).
css(‘width’, ‘0%’).
text(“Loading…”);
},
done: function(e, data) {
submitButton.prop(‘disabled’, false);
progressBar.text(“Uploading done”);
// extract key and generate URL from response
var key = $(data.jqXHR.responseXML).find(“Key”).text();
var url = ‘//’ + form.data(‘host’) + ‘/’ + key;
// create hidden field
var input = $("", { type:‘hidden’, name:
fileInput.attr
(‘name’), value: url })
form.append(input);
},
fail: function(e, data) {
submitButton.prop(‘disabled’, false);
progressBar.
css(“background”, “red”).
text(“Failed”);
}
});
});
});

Trying to upload a file produces these logs:

Started POST "/users/bazley/update_pictures" for ::1 at 2016-01-01 

21:26
:59 +0000 Processing by CharactersController#update_pictures as HTML
Parameters: {
“utf8”=>“✓”,
“authenticity_token”=>“rvhu…fhdg==”,
“standardpicture”=>{
“picture”=>#<ActionDispatch::Http::UploadedFile:0x0000010b32f530

            @tempfile=#<Tempfile:/var/folders/19/_vdcl1r913g6fzvk1l56x4km0000gn/T/RackMultipart20160101-49946-7t94p.jpg>,

            @original_filename="europe.jpg",
            @content_type="image/jpeg",
            @headers="Content-Disposition: form-data;

name=“standardpicture[picture]”;
filename=“europe.jpg”\r\nContent-Type:
image/jpeg\r\n">
},
“commit”=>“Upload pictures”,
“callsign”=>“bazley”
}

The form submits successfully, but it isn’t working because Rails
doesn’t
save the correct location (“picture”, a string) on S3; instead it thinks
the location is

"picture"=>#<ActionDispatch::Http::UploadedFile:0x0000010b32f530

You can see this in the submitted parameters. It should be something
like:

"picture"=>

//websmash.s3.amazonaws.com/uploads/220f5378-1e0f-4823-9527-3d1170089a49/europe.jpg
}, “commit”=>“Upload pictures”}

What I don’t understand is why it’s getting the parameters wrong when
all
the correct information seems to be present in the form. It clearly says

data-url="https://websmash.s3.amazonaws.com"

in the form, and the jQuery includes

url:  form.data('url'),

so what’s going wrong?

For completeness: in the controller:

before_action :set_s3_direct_post
.
.
def set_s3_direct_post
  @s3_direct_post = S3_BUCKET.presigned_post(key:

“uploads/#{SecureRandom.uuid}/${filename}”, success_action_status:
‘201’,
acl: ‘public-read’)
end

The form:

<%= form_for :standardpicture, url: update_pictures_user_path,
             html: {  id: "pic-upload", class: "directUpload",
                      data: { 'form-data' => 

(@s3_direct_post.fields),
‘url’ => @s3_direct_post.url,
‘host’ =>
URI.parse(@s3_direct_post.url).host
}
} do |f| %>


<%= f.label :picture %>
<%= f.file_field :picture, accept:
‘image/jpeg,image/gif,image/png’
%>

<%= f.submit “Upload pictures”, class: “btn btn-primary” %>
<% end %>

aws.rb initializer:

Aws.config.update({
  region: 'us-east-1',
  credentials: Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV[

‘AWS_SECRET_ACCESS_KEY’]),
})
S3_BUCKET = Aws::S3::Resource.new.bucket(ENV[‘S3_BUCKET’])