ymlを読み込んでkeyをHashのシンボルとして扱う(deep_symbolize_keys)

こんなymlがあったとして

#settings.yml
twitter:
  consumer_key:       'aaaaaaaaaaaaa'
  consumer_secret:    'bbbbbbbbbbbbb'
  oauth_token:        'ccccccccccccc'
  oauth_token_secret: 'ddddddddddddd'

settings[:twitter][:consumer_key]みたいに扱いたく
きっとActiveSuportにあるだろうと調べてみると
rails4.0.2から入った deep_symbolize_keys というのがありました。
(つい最近入ったのは意外でした)
 
以前からある symbolize_keys というメソッドでは、再帰的に変換してくれません。
上記例だと、settings[:twitter] は okでsettings[:twitter][:consumer_key] はng。
 

ソース

#activesupport-4.1.1/lib/active_support/core_ext/hash/keys.rb

class Hash
  def deep_symbolize_keys
    deep_transform_keys{ |key| key.to_sym rescue key }
  end

  def deep_transform_keys(&block)
    result = {}
    each do |key, value|
      result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value
    end
    result
  end
end

やってることは単純で、Hashだったら再帰的にkeyをto_symしているだけです。
rescueは何じゃろなと思って触ってみると
keyがnilの場合を考慮したロジックでした。

まとめ

deep_symbolize_keys 便利!
ActiveSupportソースコードリーディングは、読みやすくてよさそうです。
 
 

以下おまけ

deep_transform_keys の書き方をみていると、どうもinject化したくなってしまいました。
benchmark取ってみると、、、

class Hash
  def deep_symbolize_keys
    deep_transform_keys{ |key| key.to_sym rescue key }
  end
  
  def new_deep_symbolize_keys
    new_deep_transform_keys{ |key| key.to_sym rescue key }
  end

  def deep_transform_keys(&block)
    result = {}
    each do |key, value|
      result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value
    end
    result
  end
  
  def new_deep_transform_keys(&block)
    inject({}) do |result, (key, value)|
      result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value
      result
    end
  end
end


require 'benchmark'
h = {"a"=> "a1", "b"=> "b1"}
n = 1000000
Benchmark.bm do |x|
  x.report { (1..n).each{ h.deep_symbolize_keys } }
  x.report { (1..n).each{ h.new_deep_symbolize_keys } }
end
       user     system      total        real
   3.900000   0.050000   3.950000 (  3.977981)
   5.120000   0.030000   5.150000 (  5.173796)

あ、だめだすごく遅い。injectは遅いのか。
ということでココに書いて忘れることにします。