Rubyでxlsxファイルを読めるrooというgemでエクセルを読んでいる箇所がエラーになるようになりました。

しかもローカル環境のMacでのみ発生し、AWSのAmazonLinuxやUbuntu上では問題なく動くという罠っぷり。これのおかげでだいぶハマりました。

問題のはS3上に置いたエクセルをRubyで開こうとしたときで、コードはだいたいこんな感じでした。

client = Aws::S3::Client.new(
  region: region,
  access_key_id: access_key,
  secret_access_key: secret_access_ke,
)

file = client.get_object(bucket: bucket_name, key: 'file.xlsx')
spreadsheet = Roo::Excelx.new(file.body) # <= エラー発生

最後の行で

undefined method `bytesize' for nil:NilClass

のエラーが発生しました。

ここのfile.bodyはStringIOで、S3のファイルの中身を読むためのストリームで、少しずつ読み出す途中でエラーになっているようです。

rooやrooが内部で使っているrubyzipの中に潜ってデバッグしたけどよくわからず、issueを見てもすんなりいかず困ってたんですが、同僚に相談してたらそれっぽいissueを見つけてくれました。

Problem with open_buffer · Issue #177 · rubyzip/rubyzip
I&#39;m getting undefined method bytesize&#39; for nil:NilClass`using open_buffer. this code works: data = string_with_zip_file ::File.open(&#39;my_file.zip&#39;, &quot;wb&quot;) {|f| f.write(data)...

このURLのaalvaradoさんのコメントに↓のような記載がありまして、

Roo::Excelx.new(StringIO.new(xlsx_report.file_data))

NoMethodError: undefined method `bytesize’ for nil:NilClass

まさに今回踏んでいる現象のように見えました。この方はset_encodingでアスキー指定したら直ったということです。

Roo::Excelx.new(StringIO.new(xlsx_report.file_data).set_encoding('ASCII-8BIT'))

さて、自身のコードに戻って、file.bodyのエンコーディングを調べてみるとUTF-8だということがわかりました。

StackOverflowに倣って

file.body.set_encoding('ASCII-8BIT')
spreadsheet = Roo::Excelx.new(file.body)

とやってあげたら見事にエラーがなくなりました。このコードはLinux上でも問題なく動いたのでそのまま採用としました。