-->

How can I upload an image to S3 with rails' Ac

2020-07-18 07:33发布

问题:

As stated in the title I'm trying to upload an image to my S3 bucket with rails' Active Storage from a element that is nested within a rails form. So far I've been able to use <%= f.input :signature, type: file_field(:user, :signature), %> to upload an image with Active Storage. The User class has_one_attached :signature. The images upload correctly when I use a file_field, so that's not part of the problem.

So far my simple_form has:

  <div class="signature_pad text-center form-group">
    <div class="signature_pad_heading">
      Enter your Signature:
    </div>
    <div class="signature_pad_body">
      <canvas id="signature_pad_input" height="145px" width="370px" style="height: 145px; width: 370px;" class="border" />
    </div>
    <div class="signature_pad_footer">
      <button type="button" class="btn btn-default" onclick="signaturePad.clear()">Clear</button>
    </div>
  </div>

  <%= f.input :signature, type: file_field(:user, :signature), value: "", as: :hidden %>

  <%= f.submit "Save", class:'btn-primary btn-lg btn-md-wide',  id: "signature_pad_save"  %>

And my javascript is:

<script src="https://cdn.jsdelivr.net/npm/signature_pad@2.3.2/dist/signature_pad.min.js"></script>
<script>
const canvas = document.querySelector("canvas");
const signaturePad = new SignaturePad(canvas);

$('#signature_pad_save').click(function(event) {
    if (signaturePad.isEmpty()){
        alert('Please enter your signature.');
        event.preventDefault();
    } else {
        $('#user_signature').val(
            JSON.parse(
                signaturePad.toDataURL()  
        );
    }});
</script>

Using .toDataURL I'm able to get the base64 of the image, and everything I've read seems to point out that that's all I need to send to S3 through Active-Storage.

Finally:

What I send when I use .file_field

"signature"=>"<ActionDispatch::Http::UploadedFile:0x007f7a02ad4ef8 @tempfile=#<Tempfile:/tmp/RackMultipart20180903-3527-kked3g.png>, @original_filename="signature1.png", @content_type="image/png", @headers="Content-Disposition: form-data; name=\"user[signature]\"; filename=\"signature1.png\"\r\nContent-Type: image/png\r\n">},"

What I am sending when I try to insert the value just before the form submits

"signature"=>"#<ActiveStorage::Attached::One:0x007f7a01c8b4f0>"}

回答1:

Yes, this is possible.

Direct uploads are handled by the DirectUpload class (or ActiveStorage.DirectUpload depending on how you're accessing things). Normally DirectUpload wants a File to read the image from but conveniently enough, a File is a Blob so you can almost use a Blob instead. Active Storage only wants a few things from its "file" and almost all those things are present in Blob, the only missing thing is a name property and you can add that yourself.

So now we need to get a Blob out of the canvas. That's actually pretty easy because canvases have a toBlob method:

The HTMLCanvasElement.toBlob() method creates a Blob object representing the image contained in the canvas; this file may be cached on the disk or stored in memory at the discretion of the user agent.

First set up direct uploads as usual. Then add your own submit handler for the overall form that will look roughly like this:

const form   = your_form_element;
const canvas = your_canvas_element;
const input  = your_file_input;

canvas.toBlob(blob => {
  // Fake out DirectUpload by manually adding a name.
  blob.name = input.files[0].name;

  const uploader = new DirectUpload(blob, input.dataset.directUploadUrl);
  uploader.create((error, blob) => {
    if(error) {
      // Handle the error.
    }
    else {
      // Add the <input type="hidden"> with the signature.
      const hiddenField = document.createElement('input')
      hiddenField.setAttribute('type', 'hidden');
      hiddenField.setAttribute('value', blob.signed_id);
      form.appendChild(hiddenField);

      // And submit away...
      form.submit();
    }
  });
}, 'image/jpeg', 0.95);

That assumes you want a JPEG, you can use other content types if you want or you can match it to the original. I've also left out some of the usual submit handler boilerplate. If you have set up the usual global direct upload handlers then they'll be used here for the progress bar and such; if you don't have those then you'll have to handle that yourself, the guide linked to above has pointers on all that.



回答2:

I've had to do something similar before. I converted the base64 string back into an image file via imagemagick through the rmagick gem.

See the third answer on this question for the code: How to save a base64 string as an image using ruby.

You'd then have to add this to the create and update methods of the controller and use the @user.signature.attach method as described here.