You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
# frozen_string_literal: truerequire"bundler/inline"gemfile(true)dosource"https://rubygems.org"git_source(:github){ |repo| "https://github.com/#{repo}.git"}gem"rails",github: "rails/rails",branch: "main"gem"sqlite3"endrequire"active_record"require"minitest/autorun"require"logger"# This connection will do for database-independent bug reports.ActiveRecord::Base.establish_connection(adapter: "sqlite3",database: ":memory:")ActiveRecord::Base.logger=Logger.new(STDOUT)ActiveRecord::Schema.definedocreate_table:posts,force: truedo |t|
endcreate_table:publishers,force: truedo |t|
t.integer:post_idt.datetime:expires_atendendclassPost < ActiveRecord::Basehas_one:active_publisher,->{active},class_name: "Publisher"endclassPublisher < ActiveRecord::Basedefself.activewhere(expires_at: nil).or(expiring)enddefself.expiringwhere("expires_at > ?",Time.current)endendclassBugTest < Minitest::Test# this failsdeftest_association_reset_then_savepost=Post.createpost.create_active_publisherpost.active_publisher.update(expires_at: Time.current)post.association(:active_publisher).reset# this loads the `active_publisher` association using the already-set `@association_scope`,# which now has a stale timestamp - the publisher has already expired# but its expiry is in the future according to the timestamp set in `@association_scope`post.saveassert_nilpost.active_publisherend# this passesdeftest_association_resetpost=Post.createpost.create_active_publisherpost.active_publisher.update(expires_at: Time.current)post.association(:active_publisher).resetassert_nilpost.active_publisherendend
Expected behavior
I think both the tests in the above test case should pass, but only one does.
When we load an association (active_publisher) on an object (post), then reset that association (post.association(:active_publisher).reset), the next time the association is loaded should trigger a fresh query to find any associated record. That fresh query should invoke the association's scope realtime, so if the scope refers to Time.current, that should evaluate to the time when the association is loaded (not the time when the association was first loaded).
Actual behavior
After loading an association on an object then resetting the association, calling save on the object has the effect of loading the association again (as part of AutosaveAssocation#save_has_one_association). When the association is loaded in this way, the query to find the associated record is based on the scope as it was evaluated originally. The conditions are not re-evaluated, meaning they can be stale. In this example, an expired publisher is returned as if its expiry is still in the future, because the timestamp in the scope is now out-of-date.
The implication is (as an example) that if we do the following:
the original publisher will be deleted or its foreign key set to nil, because it will be handled as if it is still an active_publisher the second time post.create_active_publisher is called.
A workaround is to call post.association(:active_publisher).reset_scope as well as post.association(:active_publisher).reset, but would it be less surprising for reset to includereset_scope automatically?
System configuration
Rails version: 7.0.0.alpha
Ruby version: 2.7.0
The text was updated successfully, but these errors were encountered:
Steps to reproduce
Expected behavior
I think both the tests in the above test case should pass, but only one does.
When we load an association (
active_publisher
) on an object (post
), then reset that association (post.association(:active_publisher).reset
), the next time the association is loaded should trigger a fresh query to find any associated record. That fresh query should invoke the association'sscope
realtime, so if thescope
refers toTime.current
, that should evaluate to the time when the association is loaded (not the time when the association was first loaded).Actual behavior
After loading an association on an object then resetting the association, calling
save
on the object has the effect of loading the association again (as part ofAutosaveAssocation#save_has_one_association
). When the association is loaded in this way, the query to find the associated record is based on thescope
as it was evaluated originally. The conditions are not re-evaluated, meaning they can be stale. In this example, an expiredpublisher
is returned as if its expiry is still in the future, because the timestamp in thescope
is now out-of-date.The implication is (as an example) that if we do the following:
post.create_active_publisher
post.active_publisher.update(expires_at: Time.current)
post.assocation(:active_publisher).reset
post.save
post.create_active_publisher
the original
publisher
will be deleted or its foreign key set tonil
, because it will be handled as if it is still anactive_publisher
the second timepost.create_active_publisher
is called.A workaround is to call
post.association(:active_publisher).reset_scope
as well aspost.association(:active_publisher).reset
, but would it be less surprising forreset
to includereset_scope
automatically?System configuration
Rails version: 7.0.0.alpha
Ruby version: 2.7.0
The text was updated successfully, but these errors were encountered: