Rails例外処理大全
まとめ
rescue StandardError使ったら負け
コントローラのアクション内をすべて拾うのもだいたい負け
業務エラーとシステムエラーを使い分ける
ユーザーの誤入力など、ユーザーに正しく入力し直させるためにフィードバックする必要があるエラー。ステータスコードは400系になる
バリデーションエラー
if @post.save ~ else ~ の形で基本的にOK
code:post_controller.rb
def create
if @post.save
# 成功時の処理 / redirectとか, ユーザーにメール通知するとか
else
# 失敗時の処理 / @post.errorsを用いてユーザーにフィードバックする
end
end
基本的に更新系のcontrollerは上記のように終わるように書いておけば良い
権限系のチェック
authorize! メソッドを呼び、ハンドリングは外側のrescue_fromに任せると楽. RecordNotFound時のハンドリングに近い
その他
ExceptionWrapperで拾われるやつ
このマッピングで自動的に落ちてくれる
not found
ActiveRecord::RecordNotFoundが404に落ちてくれるのは有名 ( User.find() や find_by! などを使って見つからなかったときに発生する)
forbidden, unauthorized
ユーザー定義で増やすこともできる
システム開発者側が何らかの対応をする必要がある、エラー発生時にはどうしようもできないエラー。ステータスコードは500系になる
起きたら盛大にstatus code 500で死ぬ方針
ユーザーにはどうしようもできないが、エラーが起きたことだけはフィードバックする (エラー犬) 開発者が異常が起きていることを知ることができる設計にしておく
applicationレイヤー内で捕捉しない
code:ho.rb
class HogeController
rescue_from StandardError do |e|
# DON'T こういうのとかやめる. するなら必ず再度raise eして外側にぶん投げる end
def destroy
# -----
@post.destroy!
rescue
# DON'T ↑こういう例外クラス指定なしで捕捉するのをやめる. メソッド内のStandardErrorが拾われ握りつぶされる # 実際意図しているのはActiveRecord::RecordInvalidError だったりする場合が多いが、StandardErrorを拾うとNoMethodErrorやArgumentErrorなどのエラーも拾われる
end
end
まずエラー起きません、というときは例外投げる設計にしておいたほうが親切
上記では@post.destroy!は必ず成功する前提であればrescue要らない. 条件によって失敗するなら前述の業務エラーのハンドリングを使う
例えば@post.destroy!する前にポストの公開状態を非公開にしてからでないといけない、みたいな要件は、modelのバリデーションに書けるだろう
エラー通知ツール/gemに捕捉させる
このあたりは入れるだけでrack層でエラーを捕捉してくれる
その他tips
前提条件を絞るためにbefore_actionでふるい落としておくと良い
よくある例だと nested resourceの場合
/posts/:post_id/comments みたいなとき
before_action :set_post みたいなの書く (ここでは Post.find(params[:post_id] するだろうから、ActiveRecord::RecordNotFoundがraiseされ、 -> 404に落ちる)
@post が存在することを前提とできるのでアクション内のnilチェックを減らせる
他、requestのcontent typeで絞っておくとか
パラメータがおかしい(パースできないとか)ときbadrequestに落としておくとか
失敗した場合にいくつかのパターンをハンドリングする必要があるときは例外を使わず、結果を値として扱っても良い
code:_a.rb
HogeResult = Data.define(:errors) do # errorsの型は場合によって要検討。
def success? = errors.blank?
end
# --こういうクラスを定義して
def complex_method(aaa)
# なんか難しい処理
if xxx
# なんかの処理に失敗した
end
if yyy
# 別の処理に失敗した
end
return HogeResult.new(errors: nil)
end
# ↓コントローラでこう使う
class HogeController < ApplicationController
def create
result = complex_method(create_params)
if result.success?
render json: { success: true }, status: :ok
else
if result.errors.include?('XXに失敗')
# このエラーの場合だけユーザーに通知する
HogeFailedMailer.deliver_later(user, 'XXの形式が正しくありません 再度やり直してね')
end
render json: { success: false, errors: convert_to_api_errors(result.errors) }, status: :bad_request
end
end
end
2024/04/13追記 memo