Railsはデフォルトの状態ではエラー(例外)が発生すると、本番環境と開発環境でそれぞれ決められた処理が実行されます。
本番環境の場合はエラーの状態に合わせ、500エラーページか、404エラーぺージを表示します。開発環境の場合は、デカデカとエラーを表示します。
例外処理を使うと、エラーが発生したときの処理を指定することができます。
Railsの例外処理には、begin rescue, rescue, raise, retry, ensureなど専用の記述が用意されています。(正確にはRubyの例外処理です。)
ここでは、各例外処理を実例を踏まえて解説しています。
例外関連処理の早見表
各処理の内容は以下のようになります。
処理 | 内容 | 備考 |
---|---|---|
begin rescue end | beginの処理でエラーが発生した場合に、rescueを実行する。 | 末尾にendは必須 |
rescue | 上記のbeginとendを省略した形。最もよく使われる。 | rescue => 変数名 でエラーの内容が変数に入る。 |
raise | 強制的に指定したエラーを発生させる。 | beginやrescueのような節ではなく、処理として使う。 |
retry | rescueの処理の末尾に記載することで、beginに戻って再度処理を実行する。 | rescueの処理が、beginの中で初期化されると無限ループが発生する |
ensure | 例外の発生に関わらず、必ず実行する処理を記述する。 | rescueとセットで使う場合は、rescueよりも下に記述する。 |
rescue_from | railsの例外処理(他はruby)。コントローラ内で発生した例外処理をすべてとりまとめられる |
- begin rescue end, rescue, raise, retry, ensure(rescue_from)はRailsではなくRubyの例外処理です。
- rescue, ensureはbeign~endの中の節になります。(rescueはbegin endを省略可能)
- raise, retryは処理の中で使います。
begin rescue
まずは例外処理の最も基本的な形となる、begin rescue
です。(例外=エラーと考えて問題ありません)
基本構文
beginで処理を囲み、エラーが発生したときの処理をrescueの中に書きます。最後はendで閉じます。
begin
通常の処理
rescue
例外発生時の処理
end
rescueにエラーの内容を渡す
上記の例では、rescueの中に例外が発生した場合に、指定した処理を実行しています。
rescueは rescue => 変数名
と記述することで、発生したエラーの内容を変数に格納することができます。変数には、e や error が使われることが多いです。
rescueの中の例外発生時の処理で指定した変数を呼び出せば、エラーの中身を画面に表示することもできます。
begin
通常の処理
rescue => 変数
例外発生時の処理
end
begin rescueの実例
例えばコントローラの中でusersテーブルからすべてのデータを取得する処理に対して、beginとrescueを使った場合です。
例1
def index
begin
@users = User.all
rescue => error
render plain: error
end
end
テーブルからのデータ取得でエラーが発生した場合に、エラーの内容をWEBページに表示する処理になります。
renderのplainオプションは、指定した内容をテキストとしてブラウザに送信します。
Userモデルが存在しない場合は、unitiializedのエラーが返ります。
uninitialized constant UsersController::User
例2
def index
begin
@users = User.all.a
rescue => e
render json: {エラー:e}
end
end
renderのjsonオプションは、指定した内容をJSON形式でブラウザに送信します。
存在しないメソッドがある場合に、例外処理結果は以下のようになります。
{"エラー":"undefined method `a' for #\u003cClass:0x00007fe2dc0a2ff0\u003e"}
なお、例外処理を行わない場合は以下のようになります(開発環境)。
rescueで拾うエラーを指定する
rescueはエラーを拾う処理を指定すことができます。
基本構文
begin
通常の処理
rescue 拾うエラー名
例外発生時の処理
end
エラーを指定することで、エラーに対して行う処理を分けることができます。
Rubyの主なエラー一覧
エラー(クラス名) | 内容 |
---|---|
SyntaxError | 文法エラー |
NoMethodError | メソッドが存在しない |
ArgumentError | 引数が適切でない(数や型が違う) |
ZeroDivisionError | 整数を0で割った場合に発生 |
NameError | 未定義の変数や定数などを呼び出したときに発生 |
LoadError | requireやload失敗時のエラー |
RangeError | 指定した値が範囲外の時に発生 |
RegexpError | 正規表現が無効な場合に発生 |
RuntimeError | 特定の例外クラスには該当しないエラーが起こったときに発生 |
StandardError | 通常のプログラムで発生する可能性の高いエラーを束ねるクラス |
Exceptoin | 全ての例外の祖先になるクラス |
エラーを複数指定する場合は、上からrescueで指定したエラーにマッチするか検証します。
このため、上位クラスとなる、StandardErrorやExcpetionが上にある場合は、そこで例外が拾われてしまします。
rescueで拾うエラーを指定する実例
例えば以下のように、rescueを3つ記載すると次のようになります。
例1
def index
begin
@users = Test.all
rescue ZeroDivisionError
render plain: "ゼロで割り切れません"
rescue NoMethodError
render plain: "メソッドが存在しません"
rescue StandardError
render plain: "エラーです(ゼロで割る、メソッドが存在しない以外)"
end
end
ここでは、NoMethodErrorを意図的に発生させているので、2番目の「メソッドが存在しません」がブラウザに表示されます。
例2
上記の例で、User.all.a
をTest.all
に変更し、存在しないモデルからデータを取得しようとすると、ブラウザに表示されるエラーの内容も変わります。
def index
begin
@users = Test.all
rescue ZeroDivisionError
render plain: "ゼロで割り切れません"
rescue NoMethodError
render plain: "メソッドが存在しません"
rescue StandardError
render plain: "エラーです(ゼロで割る、メソッドが存在しない以外)"
end
end
ZeroDivisionErrorとNoMethodErrorのどちらにも該当しないので、3番目の「エラーです(ゼロで割る、メソッドが存在しない以外)」がブラウザに表示されます。
エラーの内容を取得する
通常のrescueと同じように => 変数名
をつければエラーの内容を取得することもできます。
def index
@users = Test.all
rescue ZeroDivisionError => e
render plain: e
rescue NoMethodError => e
render plain: e
rescue StandardError => e
render plain: e
end
▼ブラウザの表示
rescue
先ほどのbegin rescue
で、beginを省略し、rescueのみで記述することもできます。(コード量が少ないので、こちらの方が一般的です)
なお、beginの時は末尾にendが必須でしたが、rescueのみの場合はendは不要です。
基本構文
通常の処理
rescue
例外発生時の処理
rescueの実例
先ほどの、コントローラの中でusersテーブルからすべてのデータを取得する処理に対して、beginとrescueを使った処理は、rescueのみでよりシンプルにすることができます。
def index
begin
@users = User.all.a
rescue => e
render json: {エラー:e}
end
end
↓ rescueのみに変更
def index
@users = User.all.a
rescue => e
render json: {エラー: e}
end
2行減りシンプルになりました。
出力結果はもちろん同じです。
{"エラー":"undefined method `a' for #\u003cClass:0x00007fe2dc0a2ff0\u003e"}
rescue修飾子
これまでのrescueは節としての利用でした。通常の処理とエラー発生時の処理が式の場合はrescueを修飾子として使うことができます。
節や修飾子というとわかりにくいですが、改行せずに1行で記述できるということです。
式1 rescue 式2
これは以下と同じになります。
begin
式1
rescue
式2
end
もしくは、
式1
rescue
式2
raise
raiseは強制的に指定したエラーを発生させるときに使います。
raiseは通常の処理の中で使います。例えば、beginの中でraiseを使うときにその上に処理があれば、raiseまでの処理は実行されます。
基本構文
発生させるエラーのみを指定した場合は、発生したエラーの内容がエラーメッセージになります。
raise 発生させる例外クラス
raiseは第2引数にメッセージを渡すことで、表示するエラーメッセージを指定することもできます。(インスタンスの引数として渡すこともできます)
#第2引数で指定
raise 発生させる例外クラス, "エラーメッセージ"
#インスタンスの引数として指定
raise 発生させる例外クラス.new("エラーメッセージ")
raiseの実例
例1 raise単体で使用
処理の中にraiseがあるとそこで処理が止まります。
def index
@users = User.all
raise NoMethodError
end
▼ブラウザの表示
例2 raise単体で使用(エラーメッセージを指定)
エラーメッセージを指定すると、表示されるエラーの内容が変わります。
def index
@users = User.all
raise NoMethodError, "NoMethodErrorを発生させました"
end
▼ブラウザの表示
例3 raiseとrescueをセットで使う
エラーぺージを表示させるのではなく、指定した処理を行いたい場合はrescueとセットで使います。
def index
@users = User.all
raise NoMethodError, "NoMethodErrorを発生させました"
rescue =>e
render plain: e
end
▼ブラウザの表示
例4 ロールバックを許可しない
railsのロールバックを許可しないことを明示するときに、raiseを使って強制的に例外を発生することがあります。
class <クラス名> < ActiveRecord::Migration[6.1]
def up
drop_table :<テーブル名>
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
例5 raiseよりも前に処理がある場合
raiseよりも前に処理がある場合は、その処理を実行します。
begin
p "1行目"
p "2行目"
raise ZeroDevisionError
rescue
p "rescue"
end
↓ 実行結果
"1行目"
"2行目"
"rescue"
例6 raiseの下に処理がある場合
raiseよりも下に処理がある場合は、その処理をされません。raiseの後はrescueの処理に飛びます。
begin
raise ZeroDevisionError
p "1行目"
p "2行目"
rescue
p "rescue"
end
↓ 実行結果
"rescue"
retry
retryはエラーが発生したときに、もう一度beginに戻って処理を実行するループ処理です。
begin rescue endとセットで使い、rescueの中に記述します。
基本構文
begin
通常の処理
rescue
例外発生時の処理
retry
end
▼処理の流れ
beginの中の処理を試す
↓
エラーが発生
↓
rescueの中の処理に入る
↓
retryがあるのでbeginに戻る
↓
beginの処理を行う
↓
↓
retryの実例
実例1
0で割り切れないZeroDivisionErrorが発生しても、retry処理があるので、もう一度処理を実行することができます。
x = 0
error = ""
begin
calc = 5 / x
render json: {calc:calc, error: error}
rescue => e
error = e
x = 1
retry
end
▼ブラウザの表示
実例2 エラーカウンター
カウンターを設置して、rescue毎に+1すれば、エラーの発生回数を数えたり、エラーの発生回数に応じて処理を分岐することもできます。
counter = 0
begin
counter == 6 ? x=1 : x=0
calc = 8 / x
render json: {calc:calc, counter: counter}
rescue
counter += 1
retry
end
▼ブラウザの表示
retryの注意点
retry後はbegin内の処理に移ります。このときretryでセットした値が、beginの中で初期化されると無限ループが発生します。
例えば以下の場合、rescueで x=1 にしていますが、beginに戻ったあとに、x=0に戻されています。このため、ZeroDivisionErrorが無限に発生します。
begin
x = 0
calc = 5 / x
render plain: calc
rescue
x = 1
retry
end
無限ループが発生した場合は、ctrl + c でアプリケーションを終了させれば問題ありません。
コード修正後に再起動します。(もしくは、起動して、コードをしてから対象のURLにアクセスします。)
ensure
beginの中で使える処理の一つにensureがあります。ensure自体はbegin~endの中で一番最後に必ず実行する処理になります。
rescueとあわせて使うことで、例外が発生しても実行する処理を記述します。
基本構文
begin
通常の処理
rescue
例外発生時の処理
ensure
必ず実行する処理
end
ensureの実例
例1 エラーが発生する場合
通常の処理で、NoMethodErrorが発生すると、rescueに飛びますが、最後のensureの内容も実行されます。
begin
raise NoMethodError
rescue => e
p e
ensure
p "ensure"
end
↓ 実行結果
#<NoMethodError: NoMethodError>
"ensure"
例2 エラーが発生しない場合
ensureはエラーが発生しない場合でも、beginの中で一番最後に実行されます。
begin
p "1行目"
p "2行目"
ensure
p "ensure"
end
↓ 実行結果
"1行目"
"2行目"
"ensure"