LoginSignup
0
0

More than 1 year has passed since last update.

ActiveRecord の update, save は Dirty じゃないとき UPDATE 文を発行しないので別のオブジェクトで同一のレコードを扱うときに注意

Last updated at Posted at 2022-08-25

ActiveRecord#update, ActiveRecord#save はDirtyな値を持たない時、実際のUPDATEを行わない。便利機能だけど、更新対象を複数の変数に受けている場合、ちょっと予想外の挙動をするので注意。

user = User.find(42)
user.name # => 'Tarou'

current_user = User.find(42)
current_user.name # => 'Tarou'

current_user.update!(name: nil)
user.update!(name: 'Tarou') # => Dirty でないので UPDATE 文が発行されず、 DB の name は NULL のまま

current_user.reload.name # => nil
user.reload.name # => nil

👆こんなコードを書くことは普通はないだろうけど、バッチ処理とかでいろんなデータにいろいろやるときに書いてしまうかもしれない(書いた)。例えば採番を行う処理で;

# [{ id: 1, number: 1, want_to_number: true  },
#  { id: 2, number: 2, want_to_number: false },
#  { id: 3, number: 3, want_to_number: true  }]
targets = User.where(want_to_number: true)

# 何らかの処理、ここで user のレコードが取得され、number がメモリに保持される
targets.each do |user|
  user.何らかの処理
end

# number カラムのクリア
# DB の値は number = null となるが、targets の各メンバーは null となる前の number を保持したまま
# DB の値と targets の間に乖離が生じる
User.all.update_all(number: nil)

# 再採番
targets.each.with_index(1) do |user, new_number|
  # このとき targets 内の id: 1 の User オブジェクトの number は 1 となっているので、
  # new_number と一致し、dirty と判定されない
  # したがって UPDATE 文も発行されない
  user.update!(number: new_number)
end

# こうあってほしいんだけど、
# [{ id: 1, number: 1, want_to_number: true },
#  { id: 3, number: 2, want_to_number: true }]
puts targets

# 実際はこうなってる
# [{ id: 1, number: nil, want_to_number: true  },
#  { id: 2, number: nil, want_to_number: false },
#  { id: 3, number:   2, want_to_number: true  }]
puts User.all

update が Dirty なものしか UPDATE しない挙動って感覚的には何となく知ってたけど、メソッドのドキュメントには書いてないっぽい(ActiveRecord#update, ActiveRecord#save)。コードをたどっていくとActiveRecord::AttributeMethods::Dirty._update_rows がチェックしてるのがわかった。ちなみにこの挙動は設定でオフることが出来るけど、上に示したようなバグるコードはコードが悪いので設定で対処するのはやめた方が良いと思う。

config.active_record.partial_updates = false
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0