Controller内でaction内に分岐を持たずに特定条件でredirectと処理の続行を切り替えるみたいなことをやりたいときに、単純にredirect処理をprivate methodに移すみたいなやり方だと結局アクションに戻ってしまい上手いこと行かないので、ちょっと悩んだのですがブロックを使うといい感じに実装出来た気がしたのでメモ📝
サンプル実装
サンプルとして簡単にですが、なにかしらの品物に対してユーザーが支払った金額が一致していた場合に購入するような以下のようなコントローラーがあったとします。
class PurchasesController < ApplicationRecord def create @item = Item.find(item_params[:item_id]) @payment = current_user.payments.where(item: item).last return redirect_to root_path unless @payment.amount == @item.price current_user.buy!(item: @item, payment: @payment) redirect_to thanks_path end end
この処理がもりもりになってきて、不正な金額を検知してリダイレクトするような処理はアクションの外に切り出したいという想定で色々考えてみます。
🙅♀️ 単純にprivate methodに判定とリダイレクト処理を移す
単純にprivate methodに判定とリダイレクト処理を移しても、あくまでreturn先createアクションなので購入処理を中断することは出来ないのでだめです。
class PurchasesController < ApplicationRecord def create @item = Item.find(item_params[:item_id]) @payment = current_user.payments.where(item: item).last prevent_illegal_payment current_user.buy!(item: @item, payment: @payment) redirect_to thanks_path end private def prevent_illegal_payment return redirect_to root_path unless @payment.amount == @item.price end end
🤔 例外処理を使ってリダイレクトする
例外を使うみたいなやり方もありますが、ちょっと大げさすぎる気も。。。
class PurchasesController < ApplicationRecord def create @item = Item.find(item_params[:item_id]) @payment = current_user.payments.where(item: item).last prevent_illegal_payment! current_user.buy!(item: item) redirect_to thanks_path rescue IllegalPayment redirect_to root_path end private def prevent_illegal_payment! raise IllegalPayment unless @payment.amount == @item.price end end
😀 ブロックを使う
このようなケースだと、ブロックを使ってyield
で実行するようにするといい感じにaction内に分岐を持たずに書ける。
class PurchasesController < ApplicationRecord def create @item = Item.find(item_params[:item_id]) @payment = current_user.payments.where(item: item).last prevent_illegal_payment do current_user.buy!(item: @item, payment: @payment) redirect_to thanks_path end end private def prevent_illegal_payment if @payment.amount == @item.price yield else redirect_to root_path end end end
おわりに
Rubyはいろんな書き方出来て便利ですね✨