Railsにて、lock_versionカラムがあるモデルを同じ値で更新しても、lock_versionやupdated_atは更新されない

前回、Railsにて、同じ値でデータ更新した場合、タイムスタンプカラム( updated_at )が更新されないことを確認しました。
Railsにて、同じ値でデータ更新した場合、タイムスタンプカラム(updated_at)が更新されない - メモ的な思考的な

 
そんな中、 lock_version カラムがある場合の挙動に関するコメントをいただきました。

「そういえば、挙動で気になることがある」と感じたため、調べてみたときのメモを残します。

 
目次

 

環境

 
なお、今回使うモデルは、以下のコマンドで生成したものとします。

rails g model Apple name:string lock_version:integer

 

lock_versionカラムとは

最近 lock_version について同僚から教わったのですが、 lock_version という名前のカラムがあると、Railsが楽観的ロックを実行してくれます。
ActiveRecord::Locking::Optimistic

Railsガイドによると、 lock_version カラムがあるときの挙動は以下となるようです。

楽観的ロックを使うには、テーブルにlock_versionという名前のinteger型カラムが必要です。Active Recordは、レコードが更新されるたびにlock_versionカラムの値を1ずつ増やします。更新リクエストが発生したときのlock_versionの値がデータベース上のlock_versionカラムの値よりも小さい場合、更新リクエストは失敗し、以下のようにActiveRecord::StaleObjectErrorエラーが発生します。

12.1 楽観的ロック(optimistic) | Active Record クエリインターフェイス - Railsガイド

 

Rails console で動作確認

Railsガイドの記載のうち

Active Recordは、レコードが更新されるたびにlock_versionカラムの値を1ずつ増やします

という挙動について、「同じ値で更新した場合も、 lock_version カラムの値は1増えるのかな?また、もし1増えるなら updated_at も更新されるのかな?」と気になりました。

そこで、Rails console を使って動作を確認してみます。

 

データ作成時

lock_versionupdated_at とも値が設定されました。

irb(main):001:0> Apple.create(name: 'フジ')
  TRANSACTION (0.0ms)  begin transaction
  Apple Create (0.2ms)  INSERT INTO "apples" ("name", "lock_version", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["name", "フジ"], ["lock_version", 0], ["created_at", "2022-11-22 14:14:08.851151"], ["updated_at", "2022-11-22 14:14:08.851151"]]
  TRANSACTION (8.4ms)  commit transaction
=>
#<Apple:0x00007f7b57cda5a8
 id: 1,
 name: "フジ",
 lock_version: 0,
 created_at: Tue, 22 Nov 2022 14:14:08.851151000 UTC +00:00,
 updated_at: Tue, 22 Nov 2022 14:14:08.851151000 UTC +00:00>

 

同じ値でデータ更新

続いて、 name に同じ値を渡して更新してみたところ、SQLのUPDATE文は発行されませんでした。

# 取得
irb(main):002:0> a = Apple.find(1)

# 同じ値で更新
irb(main):003:0> a.update(name: 'フジ')
=> true

 
テーブルからデータを取得してみても、更新はありません。

# 取得
irb(main):004:0> a2 = Apple.find(1)
  Apple Load (0.1ms)  SELECT "apples".* FROM "apples" WHERE "apples"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=>
#<Apple:0x00007f7b57b34820

# 確認
irb(main):005:0> a2
=>
#<Apple:0x00007f7b57b34820
 id: 1,
 name: "フジ",
 lock_version: 0,
 created_at: Tue, 22 Nov 2022 14:14:08.851151000 UTC +00:00,
 updated_at: Tue, 22 Nov 2022 14:14:08.851151000 UTC +00:00>

 

別の値でデータ更新

続いて、 nameフジ から 名月 へと更新したところ、UPDATE文が発行されました。

irb(main):006:0> a2.update(name: '名月')
  Apple Update (0.2ms)  UPDATE "apples" SET "name" = ?, "updated_at" = ?, "lock_version" = ? WHERE "apples"."id" = ? AND "apples"."lock_version" = ?  [["name", "名月"], ["updated_at", "2022-11-22 14:16:24.189472"], ["lock_version", 1], ["id", 1], ["lock_version", 0]]
  TRANSACTION (8.3ms)  commit transaction
=> true

 
テーブルからデータを取得してみたところ、 lock_versionupdated_at が更新されています。

# 取得
irb(main):007:0> a3 = Apple.find(1)
  Apple Load (0.1ms)  SELECT "apples".* FROM "apples" WHERE "apples"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=>

# 確認
irb(main):008:0> a3
=>
#<Apple:0x00007f7b57cade90
 id: 1,
 name: "名月",
 lock_version: 1,
 created_at: Tue, 22 Nov 2022 14:14:08.851151000 UTC +00:00,
 updated_at: Tue, 22 Nov 2022 14:16:24.189472000 UTC +00:00>