mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Avoid extraneous preloading when loading across has_one
associations
The Preloader relies on other objects to bind the retrieved records to their parents. When executed across a hash, it assumes that the results of `preloaded_records` is the appropriate set of records to pass in to the next layer. Filtering based on the reflection properties in `preloaded_records` allows us to avoid excessive preloading in the instance where we are loading across a `has_one` association distinguished by an order (e.g. "last comment" or similar), by dropping these records before they are returned to the Preloader. In this situation, we avoid potentially very long key lists in generated queries and the consequential AR object instantiations. This is mostly relevant if the underlying linked set has relatively many records, because this is effectively a multiplier on the number of records returned on the far side of the preload. Unfortunately, avoiding the over-retrieval of the `has_one` association seems to require substantial changes to the preloader design, and probably adaptor-specific logic -- it is a top-by-group problem.
This commit is contained in:
parent
11781d0632
commit
9aa59f9d4a
3 changed files with 63 additions and 1 deletions
|
@ -38,7 +38,24 @@ module ActiveRecord
|
||||||
|
|
||||||
def preloaded_records
|
def preloaded_records
|
||||||
return @preloaded_records if defined?(@preloaded_records)
|
return @preloaded_records if defined?(@preloaded_records)
|
||||||
@preloaded_records = owner_keys.empty? ? [] : records_for(owner_keys)
|
|
||||||
|
raw_records = owner_keys.empty? ? [] : records_for(owner_keys)
|
||||||
|
seen_records_by_owner = {}.compare_by_identity
|
||||||
|
|
||||||
|
@preloaded_records = raw_records.select do |record|
|
||||||
|
assignments = []
|
||||||
|
|
||||||
|
owners_by_key[convert_key(record[association_key_name])].each do |owner|
|
||||||
|
entries = (seen_records_by_owner[owner] ||= [])
|
||||||
|
|
||||||
|
if reflection.collection? || entries.empty?
|
||||||
|
entries << record
|
||||||
|
assignments << record
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
!assignments.empty?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -200,4 +200,46 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
|
||||||
|
|
||||||
assert_equal expected, actual
|
assert_equal expected, actual
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preloading_across_has_one_constrains_loaded_records
|
||||||
|
author = authors(:david)
|
||||||
|
|
||||||
|
old_post = author.posts.create!(title: "first post", body: "test")
|
||||||
|
old_post.comments.create!(author: authors(:mary), body: "a response")
|
||||||
|
|
||||||
|
recent_post = author.posts.create!(title: "first post", body: "test")
|
||||||
|
last_comment = recent_post.comments.create!(author: authors(:bob), body: "a response")
|
||||||
|
|
||||||
|
authors = Author.where(id: author.id)
|
||||||
|
retrieved_comments = []
|
||||||
|
|
||||||
|
reset_callbacks(Comment, :initialize) do
|
||||||
|
Comment.after_initialize { |record| retrieved_comments << record }
|
||||||
|
authors.preload(recent_post: :comments).load
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal 1, retrieved_comments.size
|
||||||
|
assert_equal [last_comment], retrieved_comments
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preloading_across_has_one_through_constrains_loaded_records
|
||||||
|
author = authors(:david)
|
||||||
|
|
||||||
|
old_post = author.posts.create!(title: "first post", body: "test")
|
||||||
|
old_post.comments.create!(author: authors(:mary), body: "a response")
|
||||||
|
|
||||||
|
recent_post = author.posts.create!(title: "first post", body: "test")
|
||||||
|
recent_post.comments.create!(author: authors(:bob), body: "a response")
|
||||||
|
|
||||||
|
authors = Author.where(id: author.id)
|
||||||
|
retrieved_authors = []
|
||||||
|
|
||||||
|
reset_callbacks(Author, :initialize) do
|
||||||
|
Author.after_initialize { |record| retrieved_authors << record }
|
||||||
|
authors.preload(recent_response: :author).load
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal 2, retrieved_authors.size
|
||||||
|
assert_equal [author, authors(:bob)], retrieved_authors
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -160,6 +160,9 @@ class Author < ActiveRecord::Base
|
||||||
has_many :posts_with_signature, ->(record) { where("posts.title LIKE ?", "%by #{record.name.downcase}%") }, class_name: "Post"
|
has_many :posts_with_signature, ->(record) { where("posts.title LIKE ?", "%by #{record.name.downcase}%") }, class_name: "Post"
|
||||||
has_many :posts_mentioning_author, ->(record = nil) { where("posts.body LIKE ?", "%#{record&.name&.downcase}%") }, class_name: "Post"
|
has_many :posts_mentioning_author, ->(record = nil) { where("posts.body LIKE ?", "%#{record&.name&.downcase}%") }, class_name: "Post"
|
||||||
|
|
||||||
|
has_one :recent_post, -> { order(id: :desc) }, class_name: "Post"
|
||||||
|
has_one :recent_response, through: :recent_post, source: :comments
|
||||||
|
|
||||||
has_many :posts_with_extension, -> { order(:title) }, class_name: "Post" do
|
has_many :posts_with_extension, -> { order(:title) }, class_name: "Post" do
|
||||||
def extension_method; end
|
def extension_method; end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue