📑

ActiveSupportのsymbolize_keys!が挙動不審だった件

2020/09/24に公開約1,800字

はじめに

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はやめよう

Discussion

ログインするとコメントできます