コード日進月歩

しんくうの技術的な小話、メモ、つれづれ、など

Railsのconfigのgemでは、環境ごとのymlに分けた値はdeep mergeされている

当たり前なんですけど、部分的に切り取ると違和感があるので言葉でまとめる

環境

config (4.2.1)

gemそのもののリポジトリは以下 rubyconfig/config: Easiest way to add multi-environment yaml settings to Rails, Sinatra, Pandrino and other Ruby projects.

上書き挙動について

configのgemは公式のREADMEにも記載がある通り、config/settings.yml を起点に環境名のymlでoverrideしていく。

具体例

たとえば以下のようにconfig/settings.ymlを書いたとする

default_user_data:
  id: example_default_user
  email: default_example@example.com
  max_eat_fruits_count:
    orange: 5
    melon: 1
    banana: 4
  favorite_sports:
    - baseball
    - soccer

そして、config/settings/development.yml を以下のように記述する。

default_user_data:
  id: example_user
  email: example@example.com
  max_eat_fruits_count:
    orange: 10
    apple: 2
    banana: 5
  favorite_sports:
    - basketball
    - soccer

この場合、config/settings/development.yml の設定が上書きされるので、RAILS_ENVがdevelopmentの場合、元々のconfig/settings.ymlにも存在するキーの値は上書きされる。

pp Settings.default_user_data.id
"example_user"

またArrayもあとがちとなる。

pp Settings.default_user_data.favorite_sports
["basketball", "soccer"]

このように default_user_data 起点で考えると、idemailfavorite_sports も上書きになっているので、 max_eat_fruits_count も上書きされるように見えるがそうはならない

pp Settings.default_user_data.max_eat_fruits_count.each { |key, value| pp "#{key} => #{value}" }
"orange => 10"
"melon => 1"
"banana => 5"
"apple => 2"

(よくよく考えれば当たり前だが)これはyml全体がhashなので、上書きされるとした場合に機能として成立しなくなるのでこのような挙動となる。

mergeされたくないHashがある場合はarrayで代替するしかない

原則Hash的な記法で記述をするとmergeされてしまうし、それを防ぐと全滝的に上書きする仕組みが機能しない。そのためもし「Hash的に扱いたいが、特定階層以下はmergeしないでほしい」という場合は苦肉の策としてarrayとHashを組み合わせるなどの方法を取るしかない。

たとえば以下のようにconfig/settings.ymlを書いたとする

default_user_data:
  max_eat_fruits_count:
    - orange: 5
    - melon: 1
    - banana: 4
default_user_data:
  max_eat_fruits_count:
    - orange: 10
    - apple: 2
    - banana: 5

このようにしてやればArrayなので上書きされる

pp Settings.default_user_data.max_eat_fruits_count.each { |hash_obj| hash_obj.each{|key,value| pp "#{key} => #{value}"  }}
"orange => 10"
"apple => 2"
"banana => 5"

そして以下のように取得すればHashとして扱うことも可能となる

result =  {}.merge(*Settings.default_user_data.max_eat_fruits_count)
pp result
{:orange=>10, :apple=>2, :banana=>5}

ただかなり無理やりなので、マージを避けなければいけない値がある場合は管理手法を見直したほうが良い

関連サイト