Railsでのtransactionと例外処理:raiseの使い方を解説

はじめに

この記事の概要

こんにちは株式会社TOKOSのナオキです!
今回は、Railsのtransactionraiseを使った例外処理について解説します。

この記事では、以下の内容について取り上げています。

  • transactionメソッドの説明と基本的な使い方
  • raiseメソッドの説明と基本的な使い方
  • transactionraiseを組み合わせた使い方

対象読者

  • Rails初学者の方
  • transaction内で例外処理を行いたい方

transactionメソッドの説明と基本的な使い方

説明

transactionメソッドとは、複数のSQL文を1つのまとまった処理として扱い、全てのSQL文が正常に実行された場合にのみデータベースの変更が反映されるメソッドです。
途中でエラーや例外が発生した場合はそれまでに行われた処理が全て取り消され、データベースの状態が元に戻るためデータの不整合を防ぐことができます!

例えば、銀行の送金処理を考えてみましょう。
A銀行からB銀行に送金処理を行いたい場合、A銀行からの引き落としとB銀行への入金がセットで行われなければなりません。もしA銀行からの引き落としが完了したあとにB銀行への入金する過程でエラーが発生してしまったらデータの不整合が起こってしまいます。
transactionメソッドを使用すれば、これらの処理が全て正常に行われる(コミット)か、どちらも行われない(ロールバック)かになります。

基本的な使い方

例えば、UserモデルとShopモデルがあるとします。
transactionメソッドはActiveRecordのクラスメソッドです。つまりUserShopなどのActiveRecodeモデル自体やActiveRecord::Baseを通して使用することができます。
ActiveRecord::Baseを使用し2つのモデルを同時に更新をする必要がある場合下記のように記述します。

ActiveRecord::Base.transaction do
  # transaction内での複数の処理
  user.save!
  shop.save!
end

上記のコードでは、user.save!shop.save!の2つのデータベースの更新を1つのtransactionの中で行っています。
transaction内の全ての処理が問題なく実行されればデータベースの更新が完了します。

user.save!shop.save!のどちらかの処理で何かしらの問題がが発生したら、save!が例外を発生させます。
なのでどちらのモデルも更新されることはないです。

save!!をつけない場合は、例外が発生しないので処理は実行されます。

raiseメソッドの説明と基本的な使い方

説明

raiseメソッドとは、処理の中で意図的に例外を発生させることのできるメソッドです。
通常、プログラムの実行中に予期せぬ例外が発生すると、その時点で処理が止まり、Rubyがエラーメッセージを表示して何が問題だったのかを教えてくれます。

一方で、raiseを使用するとプログラム自体に問題がなくても、実行中に意図的に例外を発生されることができます。
これにより、異常な状態を検知し、適切なエラーハンドリングやデバッグを行いやすくなります!

raiseメソッドはデフォルトではRuntimeErrorを発生させます。
下記のようにメソッドの第一引数に例外クラス、第二引数に文字列を渡すとRuntimeError以外の例外を発生させることができ、第二引数の文字列がエラーメッセージになります。
引数に文字列だけ渡すとRuntimeErrorになります!

raise ArgumentError, "無効な値です。"

基本的な使い方

def hoge(age)
  raise "年齢をマイナス値にすることはできません" if age < 0
  puts "彼は#{age}歳です。"
end

hoge(-5)

上記は年齢がマイナス値の場合にraiseRuntimeErrorを発生させエラーメッセージで「年齢をマイナス値にすることはできません」と表示させます。

def foo(a, b)
  raise ArgumentError, "0による除算はできません。" if b == 0
  a / b
end

foo(9, 0)

上記はfooの第二引数が0の場合にraiseArgumentErrorを発生させエラーメッセージで「0による除算はできません。」と表示させています。

transactionとraiseを組み合わせた使い方

transaction内で例外が発生する場合だけでなく、プログラム上で特定の条件が満たされなかった場合に意図的に例外を発生させたい場合もがあります。
ここでraiseメソッドを使用します。raiseを使用することで、意図的に例外を発生させてtransactionを途中で停止しロールバックを行えます。

raiseの第一引数に入れられる例外クラスにActiveRecord::Rollbackがあります。
ActiveRecord::Rollbacktransactionを途中で中止し、行った更新を元に戻す(ロールバック)ために使用される特別な例外クラスです。
通常のraiseによる例外(RuntimeError)などはプログラム全体を停止させますが、ActiveRecord::Rollbackは特別で、transactionのロールバックだけを行います。なのでtransactionが中断されてもプログラム全体は停止せず残りの処理を行うことができます。

ActiveRecord::Base.transaction do
  user.save!
  if shop.invalid?
    raise ActiveRecord::Rollback, "店舗情報が無効です"
  end
  shop.save!
end

上記は、usershopを同時に更新しようとしているコードで、
invalid?メソッドを使用してshopのバリデーションを通過したか、してないかを確認しています。
もしバリデーションを通過できなかったらロールバックされます。
これにより整合性が保たれます!

さいごに

Railsでのtransactionとraiseを使用した例外処理は、データの整合性を守るために非常に重要です。
適切にtransactionを使用することで例外発生時にデータのロールバックが保証されるので不整合な状態になりません。
また、raiseを使用したエラーハンドリングを組み合わせることで、例外の原因をいち早く特定でき問題を回避することが可能になります。
ぜひ使用してみてください。