Загрузка файла Rails с использованием send_data с последующим действием - PullRequest
0 голосов
/ 09 мая 2018

Я работаю над базой кода, где мне нужно разрешить пользователю загружать PDF-документ, который уже находится на AWS S3. Я реализовал проблему загрузки, которая использовалась для предыдущей функции.

Для этой функции мне нужно обновить пользовательский интерфейс (шаг выполнения) после того, как пользователь завершил загрузку файла. Сначала я думал, что это будет так просто:

  1. Клики пользователей скачать
  2. API-вызов выполняется при загрузке файла с использованием send_data. В этом вызове API я также обновил бы модель Foo, чтобы изменить состояние, чтобы указать, что пользователь загрузил файл;
  3. Выполнить redirect_to request.referer для перезагрузки данных. Измененное состояние в Foo будет отвечать за отображение обновленного прогресса в пользовательском интерфейсе;

Я ошибочно думал, что это будет просто. Причины сложности:

  1. send_data уже выполняет рендеринг данных, поэтому я не могу обновить страницу с помощью redirect_to, так как это вызывает ошибку многократного рендеринга;
  2. send_data не работает с опцией remote: true, поэтому нет необходимости запрашивать данные по AJAX-ссылке и обновлять шаблон ERB;
  3. Я могу записать все в функцию JS on click, но это похоже на хак. Возможно, мне нужно получить файл напрямую из AWS и пропустить мой API? Я подозреваю, что у меня могут возникнуть проблемы с CORS, поскольку я не могу контролировать сервер.

Вот как выглядит мой метод загрузки rails:

def download
  attachment = Attachment.find_by_id(params[:attachment_id])
  content = send_data(
    attachment.file.read,
    filename: "#{attachment.title}.#{attachment.file.file.extension}",
    type: attachment.content_type,
    disposition: "attachment",
  )
end

Этот код js, который в основном работал, выглядит следующим образом: все соответствующие пути и имена файлов передаются в JS через атрибуты данных:

$(document).on("click", "#download", function(e){
  e.preventDefault();
  const data = $('#temp-information').data();
  var req = new XMLHttpRequest();
  req.open("GET", data.path, true);
  req.responseType = "blob";
  const filename = data.title;
  req.onload = function (event) {
    var blob = req.response;
    console.log(blob.size);
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download= filename;
    document.body.appendChild(link);
    link.click();
  };
  if (typeof window.navigator.msSaveBlob !== 'undefined') {
    // Fix to work in IE11
    window.navigator.msSaveBlob(blob, filename);
  } else {
    req.send();
  }
});

Каков наиболее эффективный и удобный способ обработки загрузки файла и обновления пользовательского интерфейса после завершения загрузки?

1 Ответ

0 голосов
/ 16 мая 2018

Не совсем ясно, чего вы пытаетесь достичь. Если вы пытаетесь позволить пользователю видеть ход загрузки, я не уверен, что вам действительно нужно что-либо делать, кроме send_data, и большинство браузеров начнут загружать файл, включая отображение индикатора выполнения.

Поскольку кажется, что вы хотите что-то сделать после завершения загрузки файла, это немного сложнее. В этой проблеме нет ничего специфичного для Rails, и подход, который вы использовали, выглядит довольно разумным для меня.

В этой теме SO вы найдете длительное обсуждение этой проблемы и различных способов, которыми люди пытались ее решить. В целом решения имеют одинаковую базовую структуру, которая заключается в простом опросе сервера.

В вашем Rails-приложении вы можете реализовать это примерно следующим образом. Предположим, вы добавили поле status в модель вложения ...

def download
  attachment = Attachment.find_by_id(params[:attachment_id])
  attachment.update(status: "downloading")
  send_data(
    attachment.file.read,
    filename: "#{attachment.title}.#{attachment.file.file.extension}",
    type: attachment.content_type,
    disposition: "attachment",
  )
  attachment.update(status: "complete")
end

Затем вы можете добавить конечную точку, которая возвращает статус файла. Таким образом, когда пользователь начинает скачивать файл, вы начинаете опрашивать эту конечную точку.

def attachment_status
  attachment = Attachment.find_by_id(params[:attachment_id])
  respond_to do |format|
    format.json do
      {status: attachment.status}
    end
  end
end

Затем в Javascript, например, используя HttpPromise :

var http = new HttpPromise;
function poll(doneFn) {
  http.get("/status.json") // you will need to set your actual status endpoint path here
      .success(function(data,xhr){
        if (data.status == "complete") {
          doneFn();
        }
      });
};
function downloadFinished(){
  // ... do whatever you want on finish here ...
};
setInterval(function(){ poll(downloadFinished) }, 5000);

Это не самая красивая вещь в мире, но она должна выполнить свою работу.

Удачи!

...