Ruby on Rails Saturday, January 9, 2016

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: 

    "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| %>
     
<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'])

--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-talk+unsubscribe@googlegroups.com.
To post to this group, send email to rubyonrails-talk@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/rubyonrails-talk/0f1c1eb0-740e-4b41-9282-79b3b41cba34%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

No comments:

Post a Comment