🔖

Ruby の Enumerable,Array,Hash#select は false を取り除かない

2024/10/02に公開

久々にハマったので戒めとして覚書。
各種 #compact 系のメソッドなんですがこれらは『要素から nil を取り除く』という挙動になります。

# 配列から要素が nil のオブジェクトを取り除く
pp [1, nil, 3, 4, nil].compact
# => [1, 3, 4]

# Hash の場合は値が nil の要素を取り除く
pp({ a: 1, b: nil, c: 3, d: nil, nil => 5 }.compact)
# => {:a=>1, :c=>3, nil=>5}

nil も取り除くので false も取り除きそうな雰囲気はあるんですが #compact では false は取り除きません。

# false はそのまま
pp [1, false, nil, 3].compact
# => [1, false, 3]

なので例えば次のようなコードを書いている場合には意図しない取り除きが行われます。

def create(name)
  {
    name: name,
    # name が nil でなく空でない場合は name_size の要素を追加する
    name_size: (name && !name.empty?) && name.size
  }.compact
end
name = "homu"

pp create("homu")   # => {:name=>"homu", :name_size=>4}
pp create(nil)      # => {}
# 空の場合も name_size が入ってほしくないが入ってしまう
pp create("")       # => {:name=>"", :name_size=>false}

こういう場合は #compact ではなくて他の手段を用いて取り除く必要があります。

def create(name)
  {
    name: name,
    name_size: (name && !name.empty?) && name.size
  }.select { _2 }   # 値が真の場合のみ絞り込む
end
name = "homu"

pp create("homu")   # => {:name=>"homu", :name_size=>4}
pp create(nil)      # => {}
# 意図する結果になる
pp create("")       # => {:name=>""}

参照

GitHubで編集を提案

Discussion