Forum: Ruby on Rails Direct to S3 upload using aws-sdk gem and jQuery-File-Upload on heroku

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
85c81137d0f4d7d1dff5d84a00a47975?d=identicon&s=25 Jem P. (jem_p)
on 2016-01-10 05:11
(Received via mailing list)
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...
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'])
This topic is locked and can not be replied to.