LoginSignup
0
0

Rails: send_file後にファイルを削除したい

Posted at

背景

  • 大きめのサイズのファイルをクライアント側にダウンロードさせたい
  • 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
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0