問題
Chefでリソースのactionに渡す引数は、SymbolもしくはSymbolのArrayである必要がある。
レシピの例:
service 'firewalld' do
action [:stop, :disable] # ここがSymbolもしくはSymbolのArray
end
Ruby DSLの場合の例:
その引数をChefのattributeに持たせる場合、Ruby DSLであれば、SymbolもしくはSymbolのArrayで保持できる。
default['foo']['service']['firewalld']['action'] = [:stop, :disable] # Ruby書式ならそのままSymbolもしくはSymbolのArrayで持てる
JSONの場合の例:
しかし、例えばchef-clientコマンドに-jオプションでJSONで渡す場合などでは、JSONにはSymbolが無い為、String(もしくはそのArray)などで記述する必要が生じる。
{
"foo": {
"service": {
"firewalld": {
"action": [
"stop",
"disable"
]
}
}
}
}
:stopや:disableは使えない。
対応0 StringもしくはSymbolなら.to_symにより対応可能
node['foo']['service'].each do |s_name, s_attr|
service s_name do
action s_attr['action'].to_sym
end
end
Stringのto_symメソッドはSymbolに変換
Symbolのto_symメソッドはSymbolのまま何もしない
Arrayにはto_symメソッドは無い
したがって、Array(Chef::Node::ImmutableArray)の場合にはエラーとなる。
NoMethodError
-------------
undefined method `to_sym' for ["stop", "disable"]:Chef::Node::ImmutableArray
対応1 Class別に処理を分ける
Stringであれば.to_symにてSymbol化が可能
Arrayであれば、.map { |e| e.to_sym }にてSymbolのArray化が可能
どちらか判らなければ、class毎に対応を変更する
node['foo']['service'].each do |s_name, s_attr|
service s_name do
if s_attr['action'].class == Chef::Node::ImmutableArray
action s_attr['action'].map { |e| e.to_sym } # もしくは.map(&:to_sym)
else
action s_attr['action'].to_sym
end
end
end
もしくは三項演算子
しかし、いずれにせよ長く読みにくい
対応2 Classを拡張する
Array(正確にはChef::Node::ImmutableArray)の場合に個々の要素毎に.to_symをしてくれれば良いので
class Chef::Node::ImmutableArray # Chef::Node::ImmutableArrayクラスを拡張 (Rubyの柔軟性は凄い)
def to_sym
self.map{ |e| e.to_sym }
# map(&:to_sym) # cookstyle -a を通すと、上の行はこう変換される。&:については参照3
end
end
node['foo']['service'].each do |s_name, s_attr|
service s_name do
action s_attr['action'].to_sym
end
end
これで値がStringでもStringのArrayでも大丈夫
単純な関数likeなmethodを書いても良さそう。