LoginSignup
6
6

More than 5 years have passed since last update.

Ruby 2.5 系で CSV.generate のバグ

Posted at

ブログ記事 からの転載です。

Ruby 2.5 系で CSV.generate を使用しようとしたら意図しない動作をして、調べてみたらバグだったのでそのまとめ。

しかし、これ、結構クリティカルなバグだと思うんですけど、全然話題になってないのが不思議(当時は話題になっていたのかもしれないけど。

CSV.generate とは

以下のような感じで CSV 形式で文字列を構築する事が出来ます。

# Ruby 2.4 で実行
require "csv"
require "pp"

result = CSV.generate do |csv|
    csv << [1, 2, 3]
    csv << ["homu", "mami", "mado"]
end
pp result
# => "1,2,3\n" + "homu,mami,mado\n"

また CSV.generate の第一引数で『その CSV の先頭文字列』を指定する事が出来ます。

require "csv"
require "pp"

csv = CSV.generate("prefix") do |csv|
    csv << [1, 2, 3]
end
pp csv
# => "prefix1,2,3\n"

# 既存の csv に対して追記したり
csv = CSV.generate(csv) do |csv|
    csv << ["homu", "mami", "mado"]
end
pp csv
# => "1,2,3\n" + "homu,mami,mado\n"

CSV.generate のバグ

で、件のバグなんですが、先ほど説明した『CSV.generate の第一引数で『その CSV の先頭文字列』を指定する』が Ruby 2.5 では動作しません。

# Ruby 2.5 で実行した場合
require "csv"

csv = CSV.generate("prefix") do |csv|
    csv << ["homu", "mami", "mado"]
end
pp csv
# => "homu,mami,mado\n"

上記のようなコードではすぐに『何かおかしい』とわかるんですが、例えば次のように『CSV データに BOM を追加する』みたいな事をしたい場合はほぼ気づきません。

require "csv"

# BOM 付き CSV データを生成したいが追加されない…
bom = "\uFEFF"
csv = CSV.generate(bom) do |csv|
    csv << ["homu", "mami", "mado"]
end

# 出力先によっては BOM がついているかどうかが視覚的にわからないのでバグを見つけるのがむずかしい…
pp csv
# => "homu,mami,mado\n"

わたしも上記のような事をやりたかったんですが、うまく動作しなくて調べてみたら既知のバグでした。

『Ruby BOM CSV』でググると CSV.generate を使ったやり方を書いているブログとかが結構ヒットするんですが、このバグに気づいてない人もいるんじゃないかなあ…。

Ruby 2.6 では修正済み

このバグは Ruby 2.6 では修正済みとなっています。

Ruby 2.5 系での対処方法

幸いにも CSV.generate は Ruby で実装されているので次のようなモンキーパッチで対処する事が出来ます。

require "csv"

# CSV.generate の実装を上書き
class CSV
    def self.generate(str=nil, **options)
        # add a default empty String, if none was given
        if str
            str = StringIO.new(str)
            str.seek(0, IO::SEEK_END)
        else
            encoding = options[:encoding]
            str      = String.new
            str.force_encoding(encoding) if encoding
        end
        csv = new(str, options) # wrap
        yield csv               # yield for appending
        csv.string              # return final String
    end
end

# これで BOM が追加される
bom = "\uFEFF"
csv = CSV.generate(bom) do |csv|
    csv << ["homu", "mami", "mado"]
end
pp csv
# => "homu,mami,mado\n"

まとめ

Ruby 2.6 はよ〜

6
6
2

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
6
6