Railsとgrapeを使用してWebAPIを作成する。
grapeでは入力パラメータのバリデーションが事細かく用意されているが、なぜか「あるパラメータが 特定の値 の場合のみ、別のパラメータをチェックする」機能がない。そんなにレアケースだろうか?
ちなみに、「あるパラメータが 存在する 場合のみ、別のパラメータをチェックする」機能はある。(given
ブロック)
バージョンはRuby(2.2), Rails(4.2), grape(0.13.0)で確認した。
例として、次のようなAPIがあったとする。dish
が"nigiri"
の場合のみamount_of_wasabi
をチェックしたい。
与えられる正しい入力は{ "dish": "nigiri", "amount_of_wasabi": "M" }
か{ "dish": "chirashi" }
のような形を想定する。
考え方としては、特定の場合のみvalidatorを追加するのは面倒なので逆に特定の場合のみ外すようにする。
class MyAPI < Grape::API
resource :sushi_menus do
params do
requires :dish, type: String, allow_blank: false, values: ["nigiri", "chirashi"]
requires :amount_of_wasabi, type: String, allow_blank: false, values: ["w/o", "S", "M", "L"]
end
before_validation do
# HACK: dishが"nigiri"以外の場合はamount_of_wasabiのvalidatorを外す
@rejected_validators = []
unless params[:dish] == "nigiri"
@rejected_validators = route_setting(:saved_validations).select { |v| v.attrs.include?(:amount_of_wasabi) }
route_setting(:saved_validations).reject! { |v| v.attrs.include?(:amount_of_wasabi) }
end
end
after_validation do
# HACK: 外したvalidatorを戻す
route_setting(:saved_validations).concat(@rejected_validators)
end
post do
# ...省略
end
end
end
これでできることはできるが、あまりにも醜悪なのでAPI設計の方を変えてしまうという手もある。
別解としての設計変更
入力を{ "dish": { "nigiri": { "amount_of_wasabi": "M" } } }
か{ "dish": { "chirashi": true } }
のような形に変更する。
class MyAPI < Grape::API
resource :sushi_menus do
params do
requires :dish, type: Hash, allow_blank: false do
optional :nigiri, type: Hash do
requires :amount_of_wasabi, type: String, allow_blank: false, values: ["w/o", "S", "M", "L"]
end
optional :chirashi, type: Boolean, values: [true]
exactly_one_of :nigiri, :chirashi
end
end
post do
# ...省略
end
end
end