Rails における transaction とは?
ざっくり言うと、ActiveRecordの処理(DBのSQL操作であるcreate, update, deleteなど)を保護するもの。
よくわからないかもしれないので、次のような入力されたパラメーターの商品を登録する際に、送料を商品の価格に応じて割引くような一連の処理があったとします。
ActiveRecord::Base.transaction do
product = Product.create!(product_params) # product_params 内のデータをもとに product インスタンスを作成する
shipping_fee = product.price / DISCOUNT_RATE # 価格を DISCOUNT_RATE で割ったものを送料 shipping_fee とする
product.update!(shipping_fee: shipping_fee) # 商品インスタンスの送料を登録(更新)する
end
end
ここでのActiveRecordの処理は2つ、
product = Product.create!(product_params)
product.update!(shipping_fee: shipping_fee)
ですね。
これらが、
ActiveRecord::Base.transaction do
end
で囲まれることで、この範囲内のActiverecordの処理は一連の処理として保護されます。
どういうことか?というと、例えば1つ目のActiveRecord処理であるproductインスタンスの作成(create!
)は成功したとして、次の送料計算でもしDISCOUNT_RATE
が0
だった場合、0除算でZeroDivisionError
が発生します。
shipping_fee = product.price / DISCOUNT_RATE # DISCOUNT_RATE = 0 なので 0除算が発生する
ZeroDivisionError: divided by 0
このようにエラーが発生すると、直ちにロールバック処理が行れ、transactionで囲まれた範囲内のActiveRecordの処理は全てもとの状態に戻ります(処理前のcreateされていない状況)。
また、その次のupdate!
の処理でエラーが発生した場合も同様で、その以前のActiveRecord処理であるcreate!
はもとの状態に戻ります。
この全てもとに戻ってくれるということが非常に大事、つまりtransactionを使う意義があるのです!
例えばお金のやり取りに関わる一連の支払処理があったとすると、全ての処理が完璧でないと一連の処理としてOKではなく、どこか1つでも処理に誤りがあればその一連の処理はすべてダメという処理が必要な場合が多々あります。
そうでないと、例えばお金を渡す人、お金を受け取る人のどちらかが得する、損することなどになりかねず、サービスとしては致命的な設計となります。
では、なぜ?何を?トリガー(引き金)にロールバックが起こるのでしょうか?
察しが良い方は気づかれていると思いますが、エラー(例外)が発生したからです。
つまりこの例外が発生することがtransactionでのロールバックが起こるトリガーになるのです!←ここ、とても重要ですよ。
以上を踏まえると、transaction内で例外が発生する実装でなければなりません。
次のセクションでは、例外を起こすために気をつける実装例を紹介いたしますので参考にしていただければと思います。
例外を発生させる実装
左側での記述では例外が発生しない(失敗してもfalse
やnil
を返すだけ)ので、右側の記述へ変更して下さい。
save => save!
create => create!
update => update!
destroy => destroy!
find_by => find_by!
それ以外に覚えておくものも書いておきます。
find # findはそのままエラーをはきます