Madogiwa Blog

主に技術系の学習メモに使っていきます。

Ruby on Rails: Controllerのaction内に分岐を持たずにredirectと処理の続行を切り替える実装をしたいときのメモ

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はいろんな書き方出来て便利ですね✨

参考

docs.ruby-lang.org