Railsにて、トランザクションの中だとモデルのid列はいつ設定されるのか確認してみた

Railsを使っている中で、「トランザクションの中だとモデルのid列はいつ設定されるのか」が気になったことから、確認してみたときのメモを残します。

目次

 

環境

  • Rails 7.1.3
  • DBはデフォルトのSQLite3

 
また、今回は以下のモデルとマイグレーションを用意します。

apple.rb

class Apple < ApplicationRecord
end

 
モデルに対するマイグレーションファイルはこちら。

class CreateApples < ActiveRecord::Migration[7.1]
  def change
    create_table :apples do |t|
      t.string :name

      t.timestamps
    end
  end
end

 

動作確認

上記のAppleモデルに対して、

の各タイミングで、モデルオブジェクトの id がどのように設定されるかを確認します。

まずは以下の rake task を作成します。

namespace :print_model_id_with_transaction do
  desc 'トランザクション利用時のmodelのidを確認'

  task run: :environment do
    # rake taskだと、`config.active_record.verbose_query_logs = true` であってもSQLログが出ないので、設定しておく
    # ActiveRecord::Base.logger = Logger.new(STDOUT)
    # Rails.logger.level = Logger::DEBUG

    ActiveRecord::Base.transaction do
      apple = Apple.build(name: 'シナノゴールド')
      puts "step1: plan.id => #{apple.id}"
      apple.save
      puts "step2: plan.id => #{apple.id}"

      raise ActiveRecord::Rollback
    end

    apple_without_transaction = Apple.build(name: '奥州ロマン')
    puts "step3: plan.id => #{apple_without_transaction.id}"
    apple_without_transaction.save
    puts "step4: plan.id => #{apple_without_transaction.id}"
  end
end

 
続いて rake task を実行したところ

$ bin/rails print_model_id_with_transaction:run
step1: plan.id => 
step2: plan.id => 1
step3: plan.id => 
step4: plan.id => 1

となりました。

これより、

という挙動と分かりました。トランザクションの中と外で設定されるタイミングは同じなようです。

 
次に、実際に発行されるSQLも見てみます。

今回は rake task で動作確認をしているため、task の先頭に以下を追加します。
rakeタスクでクエリのログを標準出力に出す | このコードわからん

task run: :environment do
  # 追加
  ActiveRecord::Base.logger = Logger.new(STDOUT)
  Rails.logger.level = Logger::DEBUG

  # 以下同じ
  ActiveRecord::Base.transaction do
  ...

 
再度 rake task を実行すると、以下のような感じで表示されました(横長になったので、タイムスタンプ部分を削除しています)。

トランザクションの中では、 commit transaction がなくてもモデルの id 列に値が設定されるようです。

$ bin/rails print_model_id_with_transaction:run
D, :   TRANSACTION (0.1ms)  begin transaction
D, :   ↳ lib/tasks/print_model_id_with_transaction.rake:10:in `block (3 levels) in <top (required)>'
step1: plan.id => 
D, :   Apple Create (0.2ms)  INSERT INTO "apples" ("name", "created_at", "updated_at") VALUES (?, ?, ?) RETURNING "id"  [["name", "シナノゴールド"], ["creat "2024-02-17 11:22:40.829966"], ["updated_at", "2024-02-17 11:22:40.829966"]]
D, :   ↳ lib/tasks/print_model_id_with_transaction.rake:12:in `block (3 levels) in <top (required)>'
step2: plan.id => 2
D, :   TRANSACTION (0.1ms)  rollback transaction
D, :   ↳ lib/tasks/print_model_id_with_transaction.rake:9:in `block (2 levels) in <top (required)>'
step3: plan.id => 
D, :   TRANSACTION (0.1ms)  begin transaction
D, :   ↳ lib/tasks/print_model_id_with_transaction.rake:20:in `block (2 levels) in <top (required)>'
D, :   Apple Create (1.0ms)  INSERT INTO "apples" ("name", "created_at", "updated_at") VALUES (?, ?, ?) RETURNING "id"  [["name", "奥州ロマン"], ["created_a2024-02-17 11:49:59.591712"], ["updated_at", "2024-02-17 11:49:59.591712"]]
D, :   ↳ lib/tasks/print_model_id_with_transaction.rake:20:in `block (2 levels) in <top (required)>'
D, :   TRANSACTION (7.3ms)  commit transaction
D, :   ↳ lib/tasks/print_model_id_with_transaction.rake:20:in `block (2 levels) in <top (required)>'
step4: plan.id => 2

 

余談:Railsのバージョンにより、トランザクション内での return 等の挙動が変わる

本題とは関係ない内容です。

Railsトランザクションまわりを調べていたところ、Railsのバージョンによりトランザクションの挙動が変わると知りました。

 

ソースコード

Githubに上げました。
https://github.com/thinkAmi-sandbox/rails_7_1_minimal_app

今回のプルリクはこちら。
https://github.com/thinkAmi-sandbox/rails_7_1_minimal_app/pull/1