【Rails】例外を発生させる方法|rescueの使い方。begin rescueとの違いやraise, retry, ensure

rails-prograshi(プロぐらし)-kv Rails
記事内に広告が含まれていることがあります。

Railsはデフォルトの状態ではエラー(例外)が発生すると、本番環境と開発環境でそれぞれ決められた処理が実行されます。

本番環境の場合はエラーの状態に合わせ、500エラーページか、404エラーぺージを表示します。開発環境の場合は、デカデカとエラーを表示します。

例外処理を使うと、エラーが発生したときの処理を指定することができます。

Railsの例外処理には、begin rescue, rescue, raise, retry, ensureなど専用の記述が用意されています。(正確にはRubyの例外処理です。)

ここでは、各例外処理を実例を踏まえて解説しています。


例外関連処理の早見表

各処理の内容は以下のようになります。

処理内容備考
begin rescue endbeginの処理でエラーが発生した場合に、rescueを実行する。末尾にendは必須
rescue上記のbeginとendを省略した形。最もよく使われる。rescue => 変数名 でエラーの内容が変数に入る。
raise強制的に指定したエラーを発生させる。beginやrescueのような節ではなく、処理として使う。
retryrescueの処理の末尾に記載することで、beginに戻って再度処理を実行する。rescueの処理が、beginの中で初期化されると無限ループが発生する
ensure例外の発生に関わらず、必ず実行する処理を記述する。rescueとセットで使う場合は、rescueよりも下に記述する。
rescue_fromrailsの例外処理(他は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未定義の変数や定数などを呼び出したときに発生
LoadErrorrequireや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.aTest.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までの処理は実行されます。

tips

Rubyには、raise以外にfailもあります。この2つは同じです。

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

(参考)【Rails】ロールバックを許可しない方法


例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の中に記述します。

注意点

retryはrescueの中でしか使えません。

rescueの外で使った場合は、SyntaxErrorが発生します。

基本構文

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をrescueとセットで使うときは、rescueよりも下に記述します。

rescueよりも前に記述すると、エラー発生時にSyntaxErrorが発生します。

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"


参考リンク

タイトルとURLをコピーしました