背景
- 大きめのサイズのファイルをクライアント側にダウンロードさせたい
-
send_data
を使ってしまうと一度に全てをメモリに読み込んでしまうので、send_file
の方を使いたい -
send_file
を使用すると、後続処理でfile.unlink
とかするとダウンロード完了前にファイルが削除されてしまう - Tempfileで作ればいつかGCで消してくれるけど、できればその場で消したい
バージョン
rails 6.1.5
ruby 2.7.5
解決法
解決法1. Rack::BodyProxy
を使用する
なんやねんそれ:https://www.rubydoc.info/gems/rack/Rack/BodyProxy
def export
CsvModule::CustomModule.new().execute do |file|
response = send_file file.path, type: Mime[:csv]
wrapped_response = Rack::BodyProxy.new(response) do
file&.unlink
end
self.response_body = wrapped_response
end
end
解決法2. send_fileのclose処理を追加する
参考:https://qiita.com/goosys/items/c8f7cbbd972bebfd8fc0
https://github.com/rails/rails/blob/6-1-stable/actionpack/lib/action_dispatch/http/response.rb#L379-L381
send_fileでstreamの送信が全て終わったあとにcloseメソッドがあればそれを使用してくれるっぽい。
github.com/rails/rails/blob/6-1-stable/actionpack/lib/action_dispatch/http/response.rb
def close
stream.close if stream.respond_to?(:close)
end
なので、closeメソッドにファイル削除処理を含むFileBody
をこっちが作ってあげればよい。
自前の環境はrails6系なのでActionDispatch::Response::FileBody
を使用する
def export
CsvModule::CustomModule.new().execute do |file|
temp_file_body = Class.new(ActionDispatch::Response::FileBody) do
attr_reader :path
def initialize(file)
@file = file
@path = @file.path
end
def close
@file&.unlink
end
end.new(file)
send_file temp_file_body.path, type: Mime[:csv]
end
end