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

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

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

1client = Aws::S3::Client.new(
2  region: region,
3  access_key_id: access_key,
4  secret_access_key: secret_access_ke,
5)
6
7file = client.get_object(bucket: bucket_name, key: 'file.xlsx')
8spreadsheet = Roo::Excelx.new(file.body) # <= エラー発生

最後の行で

undefined method `bytesize' for nil:NilClass

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

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

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

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

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

NoMethodError: undefined method `bytesize’ for nil:NilClass

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

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

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

StackOverflowに倣って

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

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