Rails: send_data でダウンロードした後に表示更新する方法


データベースの集計を CSVファイルでダウンロードする処理が必要になった。

ファイルダウンロードには send_data を使う。

今回は、非同期処理するつもりはなかったが、集計の処理には10秒くらいかかるので、ボタンをクリックして10秒くらい待ってからダウンロードが始まるので、待ち時間に「しばらくお待ちください」を表示したい。

処理開始時にこれを表示して、ダウンロードが終わったらそれを消すという動きにしたかった。

ボタンをクリック→以下のJavaScript

 $("#message").html("しばらくお待ちください");
 window.location.href = "<%= totaling_path %>"; //←集計してsend_dataするアクションのURL


そして「集計してsend_dataするアクション」の最後にメッセージを消すためのレンダリングして完了!

ところが、上記はうまく行かない。

send_data はそれ自体がレンダリングをしているので、send_dataの後にレンダリングすると Double render のエラーになるからだ。どうする?

ここに解決のヒントがあった。↓
How to trigger download with Rails send_data from AJAX post

理屈としては、処理に時間がかかっているのだから、処理とsend_dataをアクション分けして、処理が終わってから send_data のある別アクションを起動すればいいということ。

この場合に、send_data のあるアクションの起動を partial を使って、javascriptをレンダリングする形で行うというのがミソのようだ。いろんな理由から send_dataのあるアクションは javascriptの window.location を使いたいということもあり、これが都合いい。

模式的に書くと以下のような構成
↓コントローラ

# GET /totaling
def totaling
  CSV.open tempfile_path do |csv| .... などで一時ファイルとして生成
   ....時間のかかる集計処理...
   csv << data_array
  end
  @download_url = download_path(:tempfile_path => tempfile_path, :file_name => "result.csv") # ← download_path は URL /download のヘルパー関数 tempfile_pathやfile_nameは?パラメータになる
  render :partial => "downloadfile"
end

# GET /download
def download
    File.open(params[:tempfile_path], 'r') do |f|
        send_data f.read, :type => 'text/csv',:disposition => 'attachment',:filename => params[:file_name]
    end
    File.delete(params[:tempfile_path]) if File.exist?(params[:tempfile_path])
end

↓ _downloadfile.js.erb (パーシャルファイル)

  window.location.href = "<%= @download_url %>";
  $("#message").html(""); // ←ここで「しばらくおまちください」のメッセージを消す

上記で send_data の代わりに send_file を使うことはしなかった。send_file を使うと、その次の行のFile.deleteでの一時ファイルの削除が先に行われて都合がわるいことがあるからだ。上記の方法なら、記述の順番通りになる。

またApacheの場合、send_fileに必要なX-sendfile を人によってはインストールできない場合もあるだろう。

以上です。何かあればコメントをください。

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中