SeedファイルでShrineのuploadを使うと、IOError closed streamとなる場合の対処
Seed ファイルで Shrine の upload メソッドを使うと、動画や画像を紐付けたサンプルデータを生成できます。以下は、seed_fu を使ってデータを生成しているコードです。
# 全てのレコードで同じ動画を使用
video_file = File.new(Rails.root.join('spec/fixtures/files/sample.mp4'))
sample_video = VideoUploader.new(:store).upload(video_file)
Hoge.seed do |s|
s.id = 1
s.video_data = sample_video.to_json
end
Hoge.seed do |s|
s.id = 2
s.video_data = sample_video.to_json
end上記のコードだと全てのレコードで同じ動画を使用しているので、1 つのレコードを削除し紐付いている動画も削除されると、他のレコードも参照先がなくなってしまいます。
なので、下記のように Seed を書き換えてみます。
video_file = File.new(Rails.root.join('spec/fixtures/files/sample.mp4'))
# データ生成のたびに動画をアップロードして使用する
Hoge.seed do |s|
sample_video = VideoUploader.new(:store).upload(video_file)
s.id = 1
s.video_data = sample_video.to_json
end
Hoge.seed do |s|
sample_video = VideoUploader.new(:store).upload(video_file)
s.id = 2
s.video_data = sample_video.to_json
endしかし、上記のコードだと IOError: closed stream というエラーによってデータの生成に失敗します。エラーになるタイミングは、2 つ目のレコードを生成するところです。これがなぜなのかを追ってみます。
原因
IOError: closed stream というエラーの内容から 2 回目に video_file を参照しようとした際に、close されていてアップロードができなかったのかなと思いました。
rails c で確認してみます。
[16] (main)> video_file = File.new(Rails.root.join('spec/fixtures/files/sample.mp4'))
#<File:/xxx/files/sample.mp4>
-rw-r--r-- 1 tanakakenzou staff 3651 Nov 25 11:04 /xxx/files/sample.mp4
[17] (main)> video_file # この時点では参照できることを確認
#<File:/xxx/files/sample.mp4>
-rw-r--r-- 1 tanakakenzou staff 3651 Nov 25 11:04 /xxx/files/sample.mp4
[18] (main)> sample_video = VideoUploader.new(:store).upload(video_file) # upload実行
#<VideoUploader::UploadedFile storage=:store id="01f320594bafdb12f8506477378bee48.mp4" metadata={"filename"=>"sample.mp4", "size"=>3651, "mime_type"=>nil}>
[19] (main)> video_file # ここで参照できなくなっている
(pry) output error: #<IOError: closed stream>上記の通り、video_file を参照できなくなっているのは、upload を実行した後です。なので、upload の挙動を具体的に追ってみます。
定義されているのはこのあたりです。
shrine/shrine.rb at master · shrinerb/shrine
def upload(io, **options)
_enforce_io(io)
metadata = get_metadata(io, **options)
location = get_location(io, **options, metadata: metadata)
_upload(io, **options, location: location, metadata: metadata)
self.class::UploadedFile.new(
id: location,
storage: storage_key,
metadata: metadata,
)
endshrine/shrine.rb at master · shrinerb/shrine
def _upload(io, location:, metadata:, upload_options: {}, close: true, delete: false, **)
storage.upload(io, location, shrine_metadata: metadata, **upload_options)
ensure
io.close if close
File.unlink(io.path) if delete && io.respond_to?(:path) && File.exist?(io.path)
end上記のように _upload メソッドで io.close されているため、2 回目以降はファイルを参照できなくなっている、ということが分かりました。
対応
各ブロックの中で、video_file も定義する形にすると IOError: closed stream は発生しなくなります。必要であればメソッドに切り出すなどが良いかと思います。
Hoge.seed do |s|
video_file = File.new(Rails.root.join('spec/fixtures/files/sample.mp4'))
sample_video = VideoUploader.new(:store).upload(video_file)
s.id = 1
s.video_data = sample_video.to_json
end
Hoge.seed do |s|
video_file = File.new(Rails.root.join('spec/fixtures/files/sample.mp4'))
sample_video = VideoUploader.new(:store).upload(video_file)
s.id = 2
s.video_data = sample_video.to_json
end