mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Check that entire collection has been loaded before short circuiting
Currently, when checking that the collection has been loaded, only the first record is checked. In specific scenarios, if a record is fetched via an `after_initialize` hook, there is a chance that the first record has been loaded, but other records in the collection have not. In order to successfully short circuit the fetching of data, we need to verify that all of the records in the collection have been loaded. * Create test for edge case * Move `reset_callbacks` method to `cases/helper`, since it has been defined in multiple locations. Closes #37730
This commit is contained in:
parent
f2df77709f
commit
0a5b41c4c0
6 changed files with 34 additions and 25 deletions
|
@ -184,7 +184,7 @@ module ActiveRecord
|
||||||
# and attach it to a relation. The class returned implements a `run` method
|
# and attach it to a relation. The class returned implements a `run` method
|
||||||
# that accepts a preloader.
|
# that accepts a preloader.
|
||||||
def preloader_for(reflection, owners)
|
def preloader_for(reflection, owners)
|
||||||
if owners.first.association(reflection.name).loaded?
|
if owners.all? { |o| o.association(reflection.name).loaded? }
|
||||||
return AlreadyLoaded
|
return AlreadyLoaded
|
||||||
end
|
end
|
||||||
reflection.check_preloadable!
|
reflection.check_preloadable!
|
||||||
|
|
|
@ -626,6 +626,21 @@ class EagerAssociationTest < ActiveRecord::TestCase
|
||||||
assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments }
|
assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preloading_with_has_one_through_an_sti_with_after_initialize
|
||||||
|
author_a = Author.create!(name: "A")
|
||||||
|
author_b = Author.create!(name: "B")
|
||||||
|
post_a = StiPost.create!(author: author_a, title: "TITLE", body: "BODY")
|
||||||
|
post_b = SpecialPost.create!(author: author_b, title: "TITLE", body: "BODY")
|
||||||
|
comment_a = SpecialComment.create!(post: post_a, body: "TEST")
|
||||||
|
comment_b = SpecialComment.create!(post: post_b, body: "TEST")
|
||||||
|
reset_callbacks(StiPost, :initialize) do
|
||||||
|
StiPost.after_initialize { author }
|
||||||
|
comments = SpecialComment.where(id: [comment_a.id, comment_b.id]).includes(:author).to_a
|
||||||
|
comments_with_author = comments.map { |c| [c.id, c.author.try(:id)] }
|
||||||
|
assert_equal comments_with_author.size, comments_with_author.map(&:second).compact.size
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_preloading_has_many_through_with_implicit_source
|
def test_preloading_has_many_through_with_implicit_source
|
||||||
authors = Author.includes(:very_special_comments).to_a
|
authors = Author.includes(:very_special_comments).to_a
|
||||||
assert_no_queries do
|
assert_no_queries do
|
||||||
|
|
|
@ -2831,7 +2831,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
test "prevent double insertion of new object when the parent association loaded in the after save callback" do
|
test "prevent double insertion of new object when the parent association loaded in the after save callback" do
|
||||||
reset_callbacks(:save, Bulb) do
|
reset_callbacks(Bulb, :save) do
|
||||||
Bulb.after_save { |record| record.car.bulbs.load }
|
Bulb.after_save { |record| record.car.bulbs.load }
|
||||||
|
|
||||||
car = Car.create!
|
car = Car.create!
|
||||||
|
@ -2842,7 +2842,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
test "prevent double firing the before save callback of new object when the parent association saved in the callback" do
|
test "prevent double firing the before save callback of new object when the parent association saved in the callback" do
|
||||||
reset_callbacks(:save, Bulb) do
|
reset_callbacks(Bulb, :save) do
|
||||||
count = 0
|
count = 0
|
||||||
Bulb.before_save { |record| record.car.save && count += 1 }
|
Bulb.before_save { |record| record.car.save && count += 1 }
|
||||||
|
|
||||||
|
@ -2961,7 +2961,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_loading_association_in_validate_callback_doesnt_affect_persistence
|
def test_loading_association_in_validate_callback_doesnt_affect_persistence
|
||||||
reset_callbacks(:validation, Bulb) do
|
reset_callbacks(Bulb, :validation) do
|
||||||
Bulb.after_validation { |record| record.car.bulbs.load }
|
Bulb.after_validation { |record| record.car.bulbs.load }
|
||||||
|
|
||||||
car = Car.create!(name: "Car")
|
car = Car.create!(name: "Car")
|
||||||
|
@ -2996,18 +2996,4 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||||
def force_signal37_to_load_all_clients_of_firm
|
def force_signal37_to_load_all_clients_of_firm
|
||||||
companies(:first_firm).clients_of_firm.load_target
|
companies(:first_firm).clients_of_firm.load_target
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_callbacks(kind, klass)
|
|
||||||
old_callbacks = {}
|
|
||||||
old_callbacks[klass] = klass.send("_#{kind}_callbacks").dup
|
|
||||||
klass.subclasses.each do |subclass|
|
|
||||||
old_callbacks[subclass] = subclass.send("_#{kind}_callbacks").dup
|
|
||||||
end
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
klass.send("_#{kind}_callbacks=", old_callbacks[klass])
|
|
||||||
klass.subclasses.each do |subclass|
|
|
||||||
subclass.send("_#{kind}_callbacks=", old_callbacks[subclass])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -545,13 +545,6 @@ class InverseHasManyTests < ActiveRecord::TestCase
|
||||||
assert_predicate Man.joins(:interests).includes(:interests).first.interests, :any?
|
assert_predicate Man.joins(:interests).includes(:interests).first.interests, :any?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_callbacks(target, type)
|
|
||||||
old_callbacks = target.send(:get_callbacks, type).deep_dup
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
target.send(:set_callbacks, type, old_callbacks) if old_callbacks
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class InverseBelongsToTests < ActiveRecord::TestCase
|
class InverseBelongsToTests < ActiveRecord::TestCase
|
||||||
|
|
|
@ -87,6 +87,20 @@ module ActiveRecord
|
||||||
ensure
|
ensure
|
||||||
ActiveRecord::Base.has_many_inversing = old
|
ActiveRecord::Base.has_many_inversing = old
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reset_callbacks(klass, kind)
|
||||||
|
old_callbacks = {}
|
||||||
|
old_callbacks[klass] = klass.send("_#{kind}_callbacks").dup
|
||||||
|
klass.subclasses.each do |subclass|
|
||||||
|
old_callbacks[subclass] = subclass.send("_#{kind}_callbacks").dup
|
||||||
|
end
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
klass.send("_#{kind}_callbacks=", old_callbacks[klass])
|
||||||
|
klass.subclasses.each do |subclass|
|
||||||
|
subclass.send("_#{kind}_callbacks=", old_callbacks[subclass])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class PostgreSQLTestCase < TestCase
|
class PostgreSQLTestCase < TestCase
|
||||||
|
|
|
@ -59,6 +59,7 @@ class Comment < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
class SpecialComment < Comment
|
class SpecialComment < Comment
|
||||||
|
has_one :author, through: :post
|
||||||
default_scope { where(deleted_at: nil) }
|
default_scope { where(deleted_at: nil) }
|
||||||
|
|
||||||
def self.what_are_you
|
def self.what_are_you
|
||||||
|
|
Loading…
Reference in a new issue