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

84 views
Skip to first unread message

Bazley

unread,
Jan 9, 2016, 11:10:01 PM1/9/16
to Ruby on Rails: Talk
I'm trying to achieve direct to Amazon S3 upload in Rails using jQuery-File-Upload and the aws-sdk gem, and following heroku's direct to S3 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 = $("<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: 


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


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| %>
     
<div class="field">
       
<%= f.label :picture %>
       
<%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
     
</div>
     
<%= 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'])
Reply all
Reply to author
Forward
0 new messages