Railsコントローラのアクションがrenderで終わるとは限らない(翻訳)
今回はRailsのちょっとした小技をご紹介します。こんなライブラリを使っているところを想像してみてください。そのライブラリはコントローラー内でいくつかのロジックを実行し、必要な指定が不足している場合は、それに応じていくつかのテンプレートをレンダリングします。このライブラリは古くから存在する、JSON APIを使わずにHTMLテンプレートのみをレンダリングしていた時代に作成されたものだとしましょう。さて、APIコントローラに同様のロジックを追加する必要が生じました。JSONレスポンスを処理するようにライブラリを変更すべきだと思うかもしれませんが、解決方法は他にもあるのです。
コントローラのrender
メソッドを実行しても、アクションの処理を終了して制御フローを返すわけではないことを思い出しましょう。したがって、render
を呼び出した後でも、レスポンスの変更などの操作は引き続き可能です。ただし、1つのアクション内でrender
を複数回呼び出すことはできません(DoubleRenderError
という例外が発生します)。
つまり、そのライブラリにまったく触らずにアクションを拡張できるということです。そのライブラリが、例外発生時にテンプレートをレンダリングするメソッドを持つモジュールを公開しているとしましょう(これは単なる例なので、ライブラリの出来はここでは問題にしません)。
module ActivationCheck
def check_active(id)
ActivationChecker.new.call(id) # 指定のidを持つプロジェクトがアクティブならtrue、それ以外ならエラーをraise
rescue ActivationCheck::Error => exc
render("activation_check/error", message: exc.message)
false
end
end
そしてAPIコントローラが以下のようになっているとしましょう。
class ProjectsController
include ActivationCheck
def show
project = Project.find(params[:id])
if check_active(project.id)
render json: project
end
end
end
このライブラリをコントローラで使うときの問題は、check_active
メソッドがfalse
を返したときにレスポンスでステータス200 OK
のHTMLテンプレートもレンダリングされてしまうことです。JSONテンプレートを作成して、ライブラリが提供するデフォルトのHTMLテンプレートを上書きすることは可能ですが、それでもステータス200 OK
が返ってしまいます(レスポンスが成功していないのに成功したというステータスコードを返すべきではありません)。これに対処するために、フローの後半でレスポンスのステータスを直接変更してみましょう。
class ProjectsController
include ActivationCheck
def show
project = Project.find(params[:id])
if check_active(project.id)
render json: project
else # この分岐で既にActivationCheckがテンプレートをレンダリングしている
response.status = :bad_request
end
end
end
後は、JSONテンプレートを作成してデフォルトのテンプレートを上書きすれば(例: app/views/activation_check/error.json.erb
)、元のライブラリに手を加えずに、コントローラで適切なステータスコードのJSONレスポンスを返せるようになります。
お知らせ
ARKADEMY.DEVに参加してArkencyのトップクラス教育プログラムコースにアクセスしましょう!「Railsアーキテクトマスタークラス」「アンチ"IF"コース」「忙しいプログラマーのためのブログ執筆コース」「Async Remoteコース」「TDD動画クラス」「ドメイン駆動Rails動画コース」以外にもさまざまなコースが新設中です。
概要
原著者の許諾を得て翻訳・公開いたします。