コード日進月歩

しんくうの技術的な小話、メモ、つれづれ、など

Railsにてbefore_actionでredirect_toしてしまうとafter_actionなどは実行されない

actionに対しての実行なのでそのとおりといえばそのとおりなんですが備忘として。

環境

$ bin/rails --version
Rails 6.0.4.1

TL;DR

  • before_actionでredirect_toをするとafter_actionでは呼ばれない
  • 回避案はいくつかあるが、action内でredirect_toをするなどすれば回避できる

今回考えるケース

以下のように必ず出力してほしいロギングを after_action に設定し、特定の条件下でリダイレクトを行うようなケース

ケースに対してbefore_actionでredirect_toをし、after_actionでロギングする場合

コード

class RedirectTestsController < ApplicationController
  before_action :redirect_check, only: :show
  after_action :after_logging

  def index
    render json: { path: "index" }
  end

  def show
    render json: { id: params[:id] }
  end

  private

  def redirect_check
    if params[:id].to_i == 10
      logger.info("***** redirect *****")
      redirect_to "/redirect_tests/"
    end
  end

  def after_logging
    logger.info("***** must after_logging! time: #{Time.zone.now} *****")
  end

end

route.rbは以下

Rails.application.routes.draw do
  resources :redirect_tests
end

このケースにおける問題点

上記のコードで http://localhost:3000/redirect_tests/1 にアクセスすると以下のようなログが出る。

Started GET "/redirect_tests/1" for ::1 at 2022-04-23 22:46:31 +0900
Processing by RedirectTestsController#show as HTML
  Parameters: {"id"=>"1"}
***** must after_logging! time: 2022-04-23 13:46:31 UTC *****
Completed 200 OK in 1ms (Views: 0.2ms | ActiveRecord: 0.0ms | Allocations: 335)

そして http://localhost:3000/redirect_tests/10 にアクセスすると、以下のようなログが出る

Started GET "/redirect_tests/10" for ::1 at 2022-04-23 22:48:05 +0900
Processing by RedirectTestsController#show as HTML
  Parameters: {"id"=>"10"}
***** redirect *****
Redirected to http://localhost:3000/redirect_tests/
Filter chain halted as :redirect_check rendered or redirected
Completed 302 Found in 1ms (ActiveRecord: 0.0ms | Allocations: 308)


Started GET "/redirect_tests/" for ::1 at 2022-04-23 22:48:05 +0900
Processing by RedirectTestsController#index as HTML
***** must after_logging! time: 2022-04-23 13:48:05 UTC *****
Completed 200 OK in 1ms (Views: 0.2ms | ActiveRecord: 0.0ms | Allocations: 317)

上記のようにbefore_actionでredirect処理が入ると、after_actionが実行されることがない。原理としては明確でactionの処理を実行することなく終わるため、actionが終わったあとに呼び出す after_action が実行されない。

もちろんaround_actioのaction実行後の処理も呼び出されない。

改善案

いくつか方法論はありますが、before_actionとafter_actionの使い方をなるべく踏襲する形だと「レンダリング処理をコールバックでやるのを避ける」というのがあるので、今回はそちらを改善案として紹介します。

アプローチとコード

before_actionでリダイレクト処理を挟むとactionに到達する前に違うURLに行ってしまうので、遷移を挟む処理はbefore_actionでさせないようにする

class RedirectTestsController < ApplicationController
  before_action :redirect_check, only: :show
  after_action :after_logging

  def index
    render json: { path: "index" }
  end

  def show
    redirect_to "/redirect_tests/" and return if @redirect_flag
    render json: { id: params[:id] }
  end

  private

  def redirect_check
    @redirect_flag = false
    if params[:id].to_i == 10
      logger.info("***** redirect flag on *****")
      @redirect_flag = true
    end
  end

  def after_logging
    logger.info("***** must after_logging! time: #{Time.zone.now} *****")
  end

end

このやり方で http://localhost:3000/redirect_tests/10 にアクセスすると、以下のようなログが出て、ロギングがリダイレクト時にもちゃんとでる。

Started GET "/redirect_tests/10" for ::1 at 2022-04-23 22:58:14 +0900
   (0.1ms)  SELECT sqlite_version(*)
Processing by RedirectTestsController#show as HTML
  Parameters: {"id"=>"10"}
***** redirect flag on *****
Redirected to http://localhost:3000/redirect_tests/
***** must after_logging! time: 2022-04-23 13:58:14 UTC *****
Completed 302 Found in 1ms (ActiveRecord: 0.0ms | Allocations: 512)


Started GET "/redirect_tests/" for ::1 at 2022-04-23 22:58:14 +0900
Processing by RedirectTestsController#index as HTML
***** must after_logging! time: 2022-04-23 13:58:14 UTC *****
Completed 200 OK in 1ms (Views: 0.2ms | ActiveRecord: 0.0ms | Allocations: 336)

参考リンク