I found a very sharp edge case the other day in a rails 6.1 app
Essentially we have two models
class A < ApplicationRecord
has_many :bs
def special_bs
bs.joins(:c).where(c: { some_field: :some_value }).select { ... }
end
end
class B < ApplicationRecord
belongs_to :a, optional: true
has_one :c
end
class C < Application
end
When creating A
we have an after_create callback that runs before A has been committed and calls that special_bs
method. If nothing else has called the bs
association first that turns out to generate a sql query that includes something along the lines of
WHERE `bs`.`a_id` = NULL
In our particular case we had a validation that was checking the contents of bs
before the special_bs
method ran. But we recently removed it and had every un-attached B
record get assigned to the first A
record that got created. (We had data audits that let us restore things once we caught this and tracked it down.)
An interesting wrinkle, I tried to write a spec for this and couldn’t reproduce it even though I can manually recreate it all day long. What I discovered is that inside of a transaction after_create callbacks have access to the eventual value of id but, outside of one they do not.
This is such an edge of an edge case that I’m not sure if it’s even worth trying to reproduce in newer rails and submit as a bug, but it was wild to track down and correct so I thought it might be worth sharing anyway.