一時ファイルを生成するために
tempfile = Tempfile.open(...) do |f|
...
...
f.write
end
(...tempfileを使った処理...)
といったコードがありました。
このコード、だいたいの場合問題なく動くのですが、たまに作成したはずの一時ファイルがないと怒られることがありました。
実は Tempfile
オブジェクトは自身への参照がなくなり、ガベージコレクションされた際に一時ファイルを削除する仕組みがあります。実はさきほどのコードでは Tempfile
のオブジェクトへの参照が Tempfile#open
のブロックを抜けるタイミングでなくなってしまっていたのです。
変数 tempfile
があるじゃないか、と思うかもしれませんが実はこれ Tempfile
のオブジェクトではないのです。
ドキュメントを見ると、
Tempfile オブジェクトはFileクラスへのDelegatorとして定義されており、Fileクラスのオブジェクトと同じように使うことができます。
とありますが、 File
クラスで定義されているメソッドを使うと Tempfile
ではなく File
のオブジェクトが返ってくるようです。( #close
#flush
binmode
などなど) Tempfile#open
にブロックを渡した場合の戻り値はブロックの戻り値であるため、 tempfile
には File
オブジェクトがはいってしまいます。
このため、GCが走ると作成された一時ファイルは削除される状態になっているのです。
当然Fileオブジェクトを通じてその一時ファイルにアクセスしようとしても、「そんなファイルはないよ」と怒られてしまうのです。
なので、さきほどのコードは次のように直す必要があります。
tempfile = Tempfile.open(...) do |f|
...
...
f.write
f // <- Tempfileオブジェクトを返す
end
(...tempfileを使った処理...)
ちなみに、 Tempfile
には create
というメソッドがあります。こちらはブロックを渡すとブロックの中で一時ファイルを使用した処理を行い、ブロックを抜けたらすぐ一時ファイルをクリアするというものです。
Tempfile.create(...) do |tempfile|
(tempfileを使った処理)
end
こちらを使う設計になっていたほうが、いつ起きるかわからないGCに依存せず、一時ファイルの生存期間を厳密に管理できます。新規に設計を行う場合は、こちらを積極的に使うべきでしょう。