Avoid mutating cached noteable in Note#touch_noteable

What does this MR do?

Note#touch_noteable ends with noteable_object&.touch, which mutates the in-memory Issue/MergeRequest/etc. ActiveRecord instance. When that record is held via a let_it_be cache and frozen, the touch raises FrozenError.

This MR detects the frozen case and uses touch_all on a single-record relation so the SQL UPDATE is issued without mutating the cached AR instance:

return unless noteable_object

if noteable_object.frozen?
  # `noteable_object` may be a `let_it_be`-cached frozen instance in tests;
  # under freeze: true that would raise FrozenError. Use `touch_all` so the
  # UPDATE goes out without mutating the cached AR object.
  noteable_object.class.where(id: noteable_object.id).touch_all
else
  noteable_object.touch
end

Same SQL, same effect on the database; just no in-memory side effect on a cached object that happens to be frozen.

Why?

This is the highest-volume mutation site surfaced by the freeze-flip probe (#600433 (closed)): 3,168 occurrences of FrozenError originated from app/models/note.rb:596. It propagates because every test that creates a Note triggers a .touch on its noteable (Issue, MR, Epic, etc.), and let_it_be(:issue) / let_it_be(:merge_request) etc. are extremely common.

Empirically validated: the validation MR (!236528 (closed)) layered this fix together with two others on top of the probe (!236505 (closed)), and the probe pipeline's failure count dropped from 665 → 183 (a 72.5% reduction), with zero remaining occurrences of note.rb:596 in the validation traces.

Verification

References

A sibling MR (!236544 (merged) for base_label.rb) and another forthcoming for ee/app/models/ee/note.rb complete the top-3 fixes.

/cc @pedropombeiro

Merge request reports

Loading
Loading