どうも、今回はtransactionについて少しまとめておきます。今まで、transactionを使ったことがなく、結構あたふたしたので、、、。
【rails・transaction】userのcreateで、user_save!周りにtransactionを設定するパターン
結論とポイント
結露とポイントは以下の通りです。
- transactionで囲うのは、SQLが流れる処理のみ
- 例えばuser.save!など
- SQLが流れない処理を囲うのはNG
- transactionは例外を発生する処理を囲う
- 例外を発生しない場合は必要なし
- 例外が発生するため、例外に対応する処理が必要になる
例えば、以下のようなコードがあったとします。
controllers/users_contoroller.rb
def create
@user = User.new(user_params)
if @user.save!
@user.invite!(current_user)
redirect_to @user
else
render :new
end
end
この時に、user_save!とuser_invite!を考えると、、、以下のような理由からtransactionを設定した方がよさそうです。
- user_save!はuserの情報を保存する処理
- user.invite!(current_user)は、現在ログインしているcurrent_userが、新しく作られたuserに招待メールを送って、新しいuserが招待メールのリンクを踏んで認証されたら、パスワード設定などができる処理
- もしuser_save!によってuserが保存されたとしても、user_invite!の処理が失敗した時(例えばmailが新しいuserに届かなかった場合)、そのuserは使われることがないユーザーとして残ってしまう。
- なので、user_save!とuser_invite!の両方が成功していてほしい、もしどちらかがミスれば、どっちもやらなかったことにしてほしい
というわけで、以下のようにコードを書き換えます。
controllers/users_controller.rb
def create
@user = User.new(user_params)
# transactionで囲むのはSQLが流れる処理だけがベスト
ActiveRecord::Base.transaction do
@user.save!
@user.invite!(current_user)
end
redirect_to @user # transactionが成功したらそのままuser情報を表示
rescue :e # もしtransactionが失敗すると、rescueされて、例外の情報がeに入る
render :new # 失敗した後は、newのテンプレートが表示
end
以上です。