ORM

トランザクション

app/controllers/test_controller.rb
class TestController < ApplicationController
  def index
    ActiveRecord::Base.transaction do
      company = Company.create(name: 'test')

      raise ActiveRecord::Rollback
    end
  end
end

説明

\n

1トランザクションとは

\n

トランザクションは、複数のデータベース操作をまとめて「全て成功」または「全て失敗」として扱うための仕組みです。一連の処理の途中で問題が発生した場合、それまでに行われた変更をすべて元に戻すことができます。

\n \n
ポイント
\n

重要: トランザクションは同一のコントローラー内で複数のモデルの操作などを行うときに使用し、例外などの中にraise ActiveRecord::Rollbackを記入しておくことで不都合が起きた時にActiveRecord::Base.transaction do内のすべてのデータベースの変更を取り消すことができます。

\n
\n\n\n\n

2基本的な使い方

\n

トランザクションの基本的な構文は以下の通りです:

\n \n
\n
ActiveRecord::Base.transaction do\n    # データベースの操作\n    # 問題が発生したらロールバック\n    raise ActiveRecord::Rollback\nend
\n
\n \n

上記の例では、raise ActiveRecord::Rollbackの部分が動作するとデータベースのデータがActiveRecord::Base.transaction doの前の状態に戻ります。

\n\n\n\n

3実践的な例

\n

注文処理で、商品の在庫を減らしつつ注文データを作成する例:

\n \n
\n
def create_order\n  ActiveRecord::Base.transaction do\n    # 商品の在庫を減らす\n    @product = Product.find(params[:product_id])\n    if @product.stock >= params[:quantity].to_i\n      @product.stock -= params[:quantity].to_i\n      @product.save!\n      \n      # 注文データを作成\n      @order = Order.new(\n        user_id: current_user.id,\n        product_id: @product.id,\n        quantity: params[:quantity].to_i\n      )\n      \n      # 注文データが保存できなければロールバック\n      unless @order.save\n        raise ActiveRecord::Rollback\n      end\n      \n      # 支払い処理\n      payment_result = process_payment(params[:payment_info])\n      \n      # 支払いが失敗したらロールバック\n      unless payment_result\n        raise ActiveRecord::Rollback\n      end\n    else\n      # 在庫不足の場合もロールバック\n      raise ActiveRecord::Rollback\n    end\n  end\nend
\n
\n \n

この例では、商品在庫の確認・更新、注文データの作成、支払い処理のどの段階でも問題が発生したら、raise ActiveRecord::Rollbackによってすべての変更がなかったことになります。

\n\n\n\n

4条件付きロールバック

\n

条件に応じてロールバックを行う例:

\n \n
\n
ActiveRecord::Base.transaction do\n  # 会社情報を更新\n  @company = Company.find(params[:id])\n  @company.update!(name: params[:name])\n  \n  # 関連する全社員の部署情報も更新\n  @company.employees.each do |employee|\n    # 条件に合わない社員がいればロールバック\n    if employee.department == "経理" && params[:allow_accounting_change] != "1"\n      flash[:alert] = "経理部門の社員情報は変更できません"\n      raise ActiveRecord::Rollback\n    end\n    \n    employee.update!(department: params[:new_department])\n  end\n  \n  # ここまで到達すればトランザクション完了(コミット)\n  flash[:notice] = "会社情報と社員情報を更新しました"\nend
\n
\n\n\n\n

5エラーハンドリングとの組み合わせ

\n

トランザクションとエラーハンドリングを組み合わせた例:

\n \n
\n
begin\n  ActiveRecord::Base.transaction do\n    # 1つ目のモデルを作成\n    @user = User.create!(user_params)\n    \n    # 2つ目のモデルを作成\n    @profile = @user.build_profile(profile_params)\n    @profile.save!\n    \n    # 外部APIを呼び出し\n    api_response = external_signup_api(@user)\n    \n    # API呼び出しが失敗したらロールバック\n    if api_response[:status] != "success"\n      raise "API Error: #{api_response[:message]}"\n    end\n  end\n  \n  # トランザクション成功時の処理\n  redirect_to user_path(@user), notice: "ユーザー登録が完了しました"\n  \nrescue ActiveRecord::RecordInvalid => e\n  # バリデーションエラー時の処理\n  flash.now[:alert] = "登録に失敗しました: #{e.message}"\n  render :new\n  \nrescue StandardError => e\n  # その他のエラー時の処理\n  logger.error "ユーザー登録エラー: #{e.message}"\n  flash.now[:alert] = "システムエラーが発生しました"\n  render :new\nend
\n
\n\n\n
ポイント
\n

補足: トランザクションは、アトミック(不可分)な操作が必要な場面で使用します。例えば、銀行での送金処理(引き落としと入金が必ず両方成功するか両方失敗する)や、複数のテーブルに関連するデータを一貫して作成・更新する場合などに有効です。

\n