使用send_data下载Rails文件并执行后续操作

时间:2018-05-09 12:00:20

标签: javascript ruby-on-rails ruby-on-rails-4 download

我正在开发代码库,我需要允许用户下载已驻留在AWS S3上的PDF文档。我已经实现了用于以前功能的下载问题。

对于此功能,我需要在用户完成文件下载后更新UI(进度步进器)。我最初认为这很简单:

  1. 用户点击下载
  2. 使用send_data进行API调用。在此API调用中,我还会更新Foo模型以更改状态以指示用户已下载文件;
  3. 执行redirect_to request.referer重新加载数据。 Foo中更改的状态将负责显示UI中的更新进度;
  4. 我错误地认为这很简单。复杂的原因:

    1. send_data已经在渲染数据,因此我无法使用redirect_to刷新页面,因为这会触发多次渲染错误;
    2. send_data不适用于remote: true选项,因此通过AJAX链接请求数据并更新ERB模板;
    3. 我可以将所有内容写入JS on click函数,但这看起来有点像黑客。我可能需要直接从AWS检索文件并跳过我的API?我怀疑我可能会遇到CORS问题,因为我无法控制服务器。
    4. 这是我的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:

      $(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();
        }
      });
      

      什么是最有效的& rails'y处理文件下载的方式&下载完成后更新UI?

1 个答案:

答案 0 :(得分:1)

它并不是100%清楚你想要完成的事情。如果您尝试让用户看到下载进度,我不确定您是否确实需要执行除send_data之外的任何操作,然后大多数浏览器将开始下载该文件,包括显示进度杆

由于在文件下载完成后您似乎想要做某事,这有点棘手。关于这个问题没有特定的Rails,你使用的方法看起来很合理。

this SO thread上,您会发现对此问题以及人们尝试解决此问题的各种方式的冗长讨论。通常,解决方案遵循相同的基本结构,即简单地轮询服务器。

在您的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);

它不是世界上最美丽的东西,但它应该完成工作。

祝你好运!