はじめに
StringとSymbolで同一名のKeyを持つHashはやめよう
{'id' => 10, :id => 11, 'name' => 'AAA', :name => 'BBB'}
遭遇
ruby2.4+rails
Hash.transform_keys!が特定の状況でsymbolize_keys(非破壊)と互換のない挙動をする
ruby2.5.2+rails
ruby2.5で同関数が実装された事で正常に動作する
確認
ruby2.4+rails
symbolが先の場合
[1] pry(main)> {:id => 10, 'id' => 11, :name => 'AAA', 'name' => 'BBB'}.symbolize_keys
=> {:id=>11, :name=>"BBB"}
[2] pry(main)> {:id => 10, 'id' => 11, :name => 'AAA', 'name' => 'BBB'}.symbolize_keys!
=> {:id=>11, :name=>"BBB"}
symbolが後の場合
[1] pry(main)> {'id' => 10, :id => 11, 'name' => 'AAA', :name => 'BBB'}.symbolize_keys
=> {:id=>11, :name=>"BBB"}
[2] pry(main)> {'id' => 10, :id => 11, 'name' => 'AAA', :name => 'BBB'}.symbolize_keys!
=> {:id=>10, :name=>"AAA"}
ruby2.5.2+rails
symbolが先の場合
[1] pry(main)> {:id => 10, 'id' => 11, :name => 'AAA', 'name' => 'BBB'}.symbolize_keys
=> {:id=>11, :name=>"BBB"}
[2] pry(main)> {:id => 10, 'id' => 11, :name => 'AAA', 'name' => 'BBB'}.symbolize_keys!
=> {:id=>11, :name=>"BBB"}
symbolが後の場合
[1] pry(main)> {'id' => 10, :id => 11, 'name' => 'AAA', :name => 'BBB'}.symbolize_keys
=> {:id=>11, :name=>"BBB"}
[2] pry(main)> {'id' => 10, :id => 11, 'name' => 'AAA', :name => 'BBB'}.symbolize_keys!
=> {:id=>11, :name=>"BBB"}
原因
該当箇所はここ
self[yield(key)] = delete(key)
の部分、
'id'
で取得した物を:id
に対して代入し、symbolで保持していた値が失われている為。
def transform_keys!
return enum_for(:transform_keys!) { size } unless block_given?
keys.each do |key|
self[yield(key)] = delete(key)
end
self
end
def symbolize_keys!
transform_keys! { |key| key.to_sym rescue key }
end
おわりに
StringとSymbolで同一名のKeyを持つHashはやめよう