Ruby の CSV が nil を返さないようにする

Ruby の CSV ライブラリはとても便利なんだけど、ひとつだけ問題があって、CSV をパースしたときに nil を返すことがある。

つぎのような CSV をパースすると2番目のカラムが nil になる。4番目のカラムは空文字になるのに!

hoge,,fuga,"",piyo
require 'csv'

CSV.parse_line('hoge,,fuga,"",piyo')
#=> ["hoge", nil, "fuga", "", "piyo"]

なので CSV のパース結果は全部文字列だと思って使ってるとエラーになってびっくりする。

CSV.parse_line('hoge,,fuga').map(&:upcase)
#=> undefined method 'upcase' for nil (NoMethodError)

CSV を生成するときも nil と空文字で変わる:

puts ['hoge', nil, 'fuga', '', 'piyo'].to_csv
#=> hoge,,fuga,"",piyo

CSV で " でクォートする必要があるのは、値に ", や改行が含まれてる場合だけなので、空文字を "" と書く必要なんてないし、CSV 的に ,,,"", には違いがない。 なのに何故こんな変な動きになってしまっているのか。誰も嬉しくないと思うんだけども。

いちおうオプションがあって、nil_value とか quote_empty を指定すればまともになる。

CSV.parse_line('hoge,,fuga,"",piyo', nil_value: '')
#=> ["hoge", "", "fuga", "", "piyo"]

puts ['hoge', nil, 'fuga', '', 'piyo'].to_csv(quote_empty: false)
#=> hoge,,fuga,,piyo

けど、いちいち指定するのが面倒なので、CSV のデフォルト動作を変更するための gem を作った。

Gemfile に

gem 'sane_csv'

と書くか、

$ gem install sane_csv

して

require 'sane_csv'

すればオプションを指定しなくてもまともな動きをする。

require 'sane_csv'

CSV.parse_line('hoge,,fuga,"",piyo')
#=> ["hoge", "", "fuga", "", "piyo"]

puts ['hoge', nil, 'fuga', '', 'piyo'].to_csv
#=> hoge,,fuga,,piyo

まあデフォルト値を変えただけなんだけども。Ruby の動的さすばらしい。

CSV の動きを変えるのでプログラム全体に影響を与えるけど、元の動きに依存してるものなんてないだろうからいいんじゃないかな(しらんけど