【rails・transaction】userのcreateで、user_save!周りにtransactionを設定するパターン

どうも、今回は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

以上です。

Leave a Reply

Your email address will not be published.

CAPTCHA