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