RubyでYAMLファイルを読むとネストしたHashにできます。
a:
b:
c1: hogehoge
c2: fugafuga
であればこうなります。
require 'yaml'
hash = YAML.load_file('さっきのyml')
# hashは↓と同じものになる
{
a: {
b: {
c1: 'hogehoge',
c2: 'fugafuga',
}
}
}
ところでRailsの多言語対応のための仕組みI18nではYAMLを使いますよね。で、I18n.l('activerecord.errors.messages.record_invalid')
などとして、キーを渡すと翻訳が返ってきます。
このキーの部分をドットでsplit
してYAMLを読み込んだHashを辿れば目的の翻訳にたどり着けるわけですが、その逆の全てのキーを結合してフルパスみたいにしたHashが欲しくなったのでどうやってやるのかなーと調べました。
(そもそもなんて呼んだらいいのかわからないので、便宜的にフルパスとでも呼んでおきます。)
つまり、↓のようなYAMLファイルがあったら(rails-i18nのja.ymlを拝借しています)、
ja:
activerecord:
errors:
messages:
record_invalid: "バリデーションに失敗しました: %{errors}"
restrict_dependent_destroy:
has_one: "%{record}が存在しているので削除できません"
has_many: "%{record}が存在しているので削除できません"
↓こうなってほしいのです。
{
"ja.activerecord.errors.messages.record_invalid" => "バリデーションに失敗しました: %{errors}",
"ja.activerecord.errors.messages.restrict_dependent_destroy.has_one" => "%{record}が存在しているので削除できません",
"ja.activerecord.errors.messages.restrict_dependent_destroy.has_many" => "%{record}が存在しているので削除できません",
}
調べてみてさっとできそうな方法がわからなかったので、再起的に書いてみることにしました。
module HashKeyJoiner
module_function
def join(hash, divider = '.')
h = {}
each_path(hash.dup, '', divider) do |path, value|
h[path] = value
end
h
end
def each_path(object, path, divider = '.', &block)
if object.is_a?(Hash)
object.each do |key, value|
div = path.empty? ? '' : divider
next_path = [path, key].join(div)
each_path value, next_path, divider, &block
end
else
yield path, object
end
end
end
Hashを拡張するのもよいのですが、専用のモジュールにして呼ぶだけにしておきました。
Hashを辿りながら、次の子要素がHashであれば再帰処理、終端の文字列であれば新しいHashに値を追加をしています。途中経過のpathを結合していきながらたどっているので、キーのフルパスで新しいHashを作れます。
↓のように呼び出すと、
yaml = YAML.load_file('./ja.yml')
puts HashKeyJoiner.join(yaml)
↓のようにHash化されます。
{
"ja.activerecord.errors.messages.record_invalid"=>"バリデーションに失敗しました: %{errors}",
"ja.activerecord.errors.messages.restrict_dependent_destroy.has_one"=>"%{record}が存在しているので削除できません",
"ja.activerecord.errors.messages.restrict_dependent_destroy.has_many"=>"%{record}が存在しているので削除できません",
...
}
使いみちはあまりなさそうですが、ひとまず僕の目的は達成。