Railsにおける例外処理。例外とエラーは違う?

f:id:endoakak:20200725103819j:plain

例外とは?エラーとは違う?

例外とは、ある処理を行った結果が期待したものと違い、異常なものであるような状況のことです。

例外とエラーは違うのか、気になって調べてみましたが、どうやら明確な共通認識はなさそうです。そもそもどういう文脈で使うのかによっても多少の違いがありそうだし。ただ、javaに関しては例外とエラーははっきり区別されてるらしい。
ということで私の今のところの結論としては、例外とエラーはほとんど一緒、強いて言えば、例外は想定内の異常事態でエラーは想定外の異常事態、という感じです。

例外への対応

例外に対する対応としては2パターンあります。

一つ目は、異常事態が起こったところだけ特別に処理をして、それまで行っていた処理を続行する方法です。

二つ目は、異常事態が起こったところで特別な処理をして、それまでに行った処理もなかったことにして中止する方法です。

例外が発生するプログラム

まずは例外が発生するプログラムを用意します。

railsで新規アプリケーションを作成し、usersテーブルを用意しました。カラムはnameのみで、空では登録できないようにモデルにバリデーションをかけておきます。

class User < ApplicationRecord
  validates :name, presence: true
end

そして、lib/tasksにファイルを作成し、次のようなタスクを設定しました。

task error: :environment do
  User.create!(name: "1")
  User.create!(name: "")
  User.create!(name: "3")
end

createメソッドに「!」をつけることで、うまく保存できなかったときに、例外が発生します。
このタスクでは2番目の処理でnameの中身が空になっているので、例外が発生するはずです。

これを実行してsequel proでテーブルの様子を確認しました。

f:id:endoakak:20200722193353p:plain

1番目の処理が終わった後に2番目の処理で例外が発生したため、name: "1"のみが保存されています。

例外を飛ばして処理を続行するパターン

2番目の処理を飛ばして3番目の処理を行うために使うのがbeginrescueになります。例外が発生しそうな場所をbeginの中に入れ、例外が発生した時の対応をrescue内に記述しておくことで、処理を進めることができます。

先ほどの例ではこんな感じになります。

task begin: :environment do
  User.create!(name: "1")
  begin
    User.create!(name: "")
  rescue
    puts "Name can't be blank"
  end
  User.create!(name: "3")
end

これを実行してみました。

f:id:endoakak:20200722193810p:plain

今回は、name: "3"も保存されています。2番目の処理で例外が発生しても、それを飛ばして3番目の処理に進んだことがわかると思います。

処理を中止して取り消すパターン

次に、例外が発生したらそれまで行っていた処理も取り消してしまう方法をみていきます。

この場合、transactionを使います。transactionを使うことで処理をひとまとめにするので、全部成功か全部失敗かのどちらかになります。

先ほどの例ではこのようになります。

task transaction: :environment do
  ActiveRecord::Base.transaction do
    User.create!(name: "1")
    User.create!(name: "")
    User.create!(name: "3")
  end
end

これを実行しても、テーブルには何も保存されません。

f:id:endoakak:20200722195852p:plain

まとめ

  • 例外とは期待と異なる結果のこと
  • 例外を飛ばして処理を続行するときはbeginとrescueを使う
  • 例外が起こったらそれまでの処理も取り消すにはtransactionを使う