こんにちは、河野です。
近々勉強会での発表を控えているのですが、YAMLでアウトラインを書いてHTMLに変換したら良いんじゃね?と思いつき、rubyでスクリプトを書いていたんですがハマってしまいました。YAMLで書いたファイルを読み込んで、erbを使って出力しようとしてたんですが、どうもうまく行きません。
YAML
1 2 3 |
hoge: boo fuga: foo piyo: woo |
RUBY
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# coding: utf-8 require 'yaml' require 'erb' require 'pp' def main raw_yaml = ARGF.read() obj = YAML.load(raw_yaml) pp obj template = <<TEMPLATE hoge = <%= obj[:hoge] %> fuga = <%= obj[:fuga] %> piyo = <%= obj[:piyo] %> TEMPLATE ERB.new(template).run(binding) end if __FILE__ == $0 main end |
出力
1 2 3 4 5 |
$ cat test.yml | ruby test.rb {"piyo"=>"woo", "fuga"=>"foo", "hoge"=>"boo"} hoge = fuga = piyo = |
ppでは出力されていますが、erbでは出力されていません。なんでー!としばらく悩んでいたんですが、答えは単純でした。
YAMLで読み込んだハッシュのキーは文字列("hoge"
)になっているので、シンボル(:hoge
)で参照しても対応する値が見つからないんですね。ppで出力した内容を良く見ると一目瞭然なんですけどね…気づくのに時間がかかってしまいました。
キーを文字列にすれば解決なんですけど、シンボルで書きたいので色々と模索してみました。
Rails由来のsymbolize_keys
RailsだとHashを拡張して、symbolize_keysというメソッドが使えるようになっています。名称の通り、キーをSymbolに変換してくれるメソッドです。今回みたいにRails使ってるわけじゃないんだけど…という時には、RailsのHashをコピペ参考にしてsymbolize_keysを実装して使うこともあるようなので、僕もやってみました。
RUBY
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# coding: utf-8 require 'yaml' require 'erb' require 'pp' class Hash def symbolize_keys! keys.each do |key| self[(key.to_sym rescue key) || key] = delete(key) end self end end def main raw_yaml = ARGF.read() obj = YAML.load(raw_yaml) pp obj.symbolize_keys! # ここで変換 template = <<TEMPLATE hoge = <%= obj[:hoge] %> fuga = <%= obj[:fuga] %> piyo = <%= obj[:piyo] %> TEMPLATE ERB.new(template).run(binding) end if __FILE__ == $0 main end |
出力
1 2 3 4 5 |
$ cat test.yml | ruby test.rb {:hoge=>"boo", :fuga=>"foo", :piyo=>"woo"} hoge = boo fuga = foo piyo = woo |
当たり前ですが、ちゃんと出力されました!
YAML側にシンボルを書いてみた
とりあえず上手く行ったんですが、ちょっとYAML使いたいな~って度にHashを拡張するのも面倒です。もしかして、YAML側でシンボルのように書いてみたら、行けるんじゃないでしょうか?
YAML
1 2 3 |
:hoge: boo :fuga: foo :piyo: woo |
RUBY(最初と同じです)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# coding: utf-8 require 'yaml' require 'erb' require 'pp' def main raw_yaml = ARGF.read() obj = YAML.load(raw_yaml) pp obj template = <<TEMPLATE hoge = <%= obj[:hoge] %> fuga = <%= obj[:fuga] %> piyo = <%= obj[:piyo] %> TEMPLATE ERB.new(template).run(binding) end if __FILE__ == $0 main end |
出力
1 2 3 4 5 |
$ cat test.yml | ruby test.rb {:hoge=>"boo", :fuga=>"foo", :piyo=>"woo"} hoge = boo fuga = foo piyo = woo |
で、でたーーーー!!!
ノリで書いてみたんで、てっきりパースエラーでも起きるかと思っていましたが、ちゃんと出力されました。
まさかと思って調べたところ、リファレンスにも載っていました。
標準添付の yaml 関連ライブラリには 1.8 系、1.9 系ともに以下のような Ruby 独自の拡張、制限があります。標準添付ライブラリ以外で yaml を扱うラ イブラリを使用する場合などに注意してください。
・ ":foo" のような文字列はそのまま Symbol として扱える
なるほど~。
Hashieというのがある
:hoge: boo
と書けるのは良いんですが、やっぱりこれってYAMLのRuby方言ですよね。YAMLは特定の言語に依存したフォーマットではないんで、他の言語で使うときに問題になりそうです。
そこで、さらにググってみると、Hashieというライブラリを発見しました。Hashieでは、キーをシンボルとして扱ったり、さらにプロパティとしてもアクセスできるようになります。
RUBY
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# coding: utf-8 require 'yaml' require 'erb' require 'pp' require 'hashie' def main raw_yaml = ARGF.read() obj = Hashie::Mash.new(YAML.load(raw_yaml)) # Hashie::Mashを使います pp obj # template も少し変えてみます template = <<TEMPLATE hoge = <%= obj["hoge"] %> fuga = <%= obj[:fuga] %> piyo = <%= obj.piyo %> TEMPLATE ERB.new(template).run(binding) end if __FILE__ == $0 main end |
出力
1 2 3 4 5 |
$ cat test.yml | ruby test.rb {"hoge"=>"boo", "fuga"=>"foo", "piyo"=>"woo"} hoge = boo fuga = foo piyo = woo |
ppではキーは文字列のままですが、erbでもちゃんと出力できてますね。
追記:ActiveSupport使えば良いですよ
ActiveSupportを使ったらどう?という指摘をいただいたので、動作確認してみました。ライブラリ使っても良いんだったら、素直にActiveSupportを使うのがスムーズですね。
※確認用のYAMLに間違いがあったので、やり直しました。訂正してます。at 2012/11/09 13:00
RUBY
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# coding: utf-8 require 'yaml' require 'erb' require 'pp' require 'active_support/all' def main raw_yaml = ARGF.read() obj = HashWithIndifferentAccess.new(YAML.load(raw_yaml)) pp obj template = <<TEMPLATE hoge = <%= obj[:hoge] %> fuga = <%= obj[:fuga] %> piyo = <%= obj[:piyo] %> TEMPLATE ERB.new(template).run(binding) end if __FILE__ == $0 main end |
出力
1 2 3 4 5 |
$ cat test.yml | ruby test.rb {"hoge"=>"boo", "fuga"=>"foo", "piyo"=>"woo"} hoge = boo fuga = foo piyo = woo |
ActiveSupportを使う場合はHashが拡張されますHashWithIndifferentAccessのインスタンスの引数にハッシュを渡します。それ以外は、特に何も気にせずキーをシンボルにしても値が取得されました。
まとめ
というわけで、RubyからYAMLを扱うときに、ハッシュのキーで悩んだら以下のどれかになりそうです。
- キーは文字列がデフォルトなのでシンボルを使うのは諦める
- symbolize_keysを自前で実装してシンボルに変換
- YAML側にシンボルで書く
- Hashieを使って自由に扱う
- ActiveSupportのHashWithIndifferentAccessを使ってシンボルに変換
Hashie便利なので、今回はHashieを使ってみようと思います!