Search code examples
ruby-on-railsjsonfile-uploadruby-on-rails-5rails-activestorage

Manual uploads using Active Storage with JSON


I am trying to make a general files uploader for all the files and images for my Rails app. The uploader function is the same like the popular JS uploaders such as Filestack or Uppy Uploader, where the users just select any file on their browser, then the app will process the file and return back the file's URL after it has been uploaded. Now, with this URL, we can then attach it to any form/WYSIWYG editor/wherever we like such as in Chat message etc.

For this purpose, my approach is to do a manual upload using Rails Active Storage.

So firstly, I make a Class called Uploader which is linked to Rails Active Storage:

app/models/uploader.rb

class Uploader < ApplicationRecord
  has_one_attached :file
end

Then, I created a Controller for this Uploader with an action called "file", where all the files will be processed:

config/routes.rb

post 'uploaders/file' => 'uploaders#file'

app/controllers/uploaders_controller.rb

  def file
    blob = ActiveStorage::Blob.create_after_upload!(
      io: params[:uploader][:file],
      filename: params[:uploader][:file].original_filename,
      content_type: params[:uploader][:file].content_type
    )

    @filelink = url_for(blob)

    respond_to do |format|
      format.html { redirect_back(fallback_location: root_path) }
      format.js
    end
  end

Finally, this is the view of this Uploader:

app/views/new.html.erb

<h3>Select Files To Upload</h3>

<%= form_for @uploader, url: uploaders_file_path(@uploader), remote: true do |f| %>
  <%= f.file_field :file, direct_upload: true,  class: "form-control" %>

  <%= f.button type: "submit", id: "submit-uploader", class: "btn btn-primary btn-md", data: {disable_with: "Uploading..."} do %>
    Save
  <% end %>
<% end %>

QUESTION

So, my question is how do I use JSON in the above codes so that I could fetch the uploaded file URL?

To start with, I have changed the above codes, to these:

app/controllers/uploaders_controller.rb

  def file
    blob = ActiveStorage::Blob.create_after_upload!(
      io: params[:uploader][:file],
      filename: params[:uploader][:file].original_filename,
      content_type: params[:uploader][:file].content_type
    )

    render json: { filelink: url_for(blob) }
  end

app/views/new.html.erb

<%= form_for @uploader, url: uploaders_file_path(@uploader), remote: true, :html => {:'data-type' => 'json', :multipart => true} do |f| %>
  <%= f.file_field :file, direct_upload: true,  class: "form-control" %>

  <%= f.button type: "submit", id: "submit-uploader", class: "btn btn-primary btn-md", data: {disable_with: "Uploading..."} do %>
    Save
  <% end %>
<% end %>

<script type="text/javascript">
  $("#submit-uploader").click(function(e){
    e.preventDefault();

    $.ajax({
      type: "POST",
      dataType: "script",
      url: $(this).parents("form").attr("action"),
      contentType: 'application/json',
      data: JSON.stringify({ file: $("#uploader_file"), _method: 'post' })
    }).done(function( data ){
      console.log(data);
    });
  });
</script>

Now, I am using javascript to submit the active storage form through JSON. But clearly something is missing as the script does not handle any active storage at the moment. So, how do i solve this?

Thanks!


Solution

  • You can send file with ajax, here is an example:

    <%= form_tag uploaders_file_path(@uploader), :html => {:multipart => true} do |f| %>
      <%= f.file_field :file, direct_upload: true,  class: "form-control" %>
    
      <%= f.button type: "submit", id: "submit-uploader", class: "btn btn-primary btn-md", data: {disable_with: "Uploading..."} do %>
        Save
      <% end %>
    <% end %>
    
    <script type="text/javascript">
      // trigger the form submit 
      $("form").submit(function(evt){   
        evt.preventDefault();
        // get the input with file
        var formData = new FormData($(this)[0]);
    
        $.ajax({
           url: "<%= uploaders_file_path(@uploader) %>",
           type: 'POST',
           data: formData,
           async: false,
           cache: false,
           contentType: false,
           enctype: 'multipart/form-data',
           processData: false,
           success: function (response) {
             alert(response);
           }
       });
       return false;
      })
    </script>