class ActiveRecord::Associations::Preloader
:nodoc:
and is therefore only used when necessary.
This could result in many rows that contain redundant data and it performs poorly at scale
WHERE ‘books`.`title` = ’Illiad’
LEFT OUTER JOIN ‘books` ON `authors`.`id` = `books`.`author_id`
FROM `authors`
`books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2
=> SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2,
Author.includes(:books).where(books: {title: ’Illiad’}).to_a
Record will fall back to a slightly more resource-intensive single query:
However, if there is a WHERE clause that spans across tables Active
arbitrarily many SQL queries made.
the second. Depending on the number of associations involved there can be
Active Record saves the ids of the records from the first query to use in
=> SELECT ‘books`.* FROM `books` WHERE `author_id` IN (2, 5)
=> SELECT `authors`.* FROM `authors` WHERE `name` IN (’bell hooks’, ‘Homer’)
Author.includes(:books).where(name: [‘bell hooks’, ‘Homer’]).to_a
multiple queries like this:
When you load an author with all associated books Active Record will make
end
# columns: title, sales, author_id
class Book < ActiveRecord::Base
end
has_many :books
# columns: name, age
class Author < ActiveRecord::Base
Suppose that you have the following two Active Record models:
Implements the details of eager loading of Active Record associations.
def branches
def branches @tree.children end
def call
def call Batch.new([self], available_records: @available_records).call loaders end
def empty?
def empty? associations.nil? || records.length == 0 end
def initialize(records:, associations:, scope: nil, available_records: [], associate_by_default: true)
queries by reusing in-memory objects. The optimization is only applied
associations before querying the database. This can save database
will try to use the objects in this array to preload the requested
+available_records+ is an array of ActiveRecord::Base. The Preloader
[ :books, { author: :avatar } ]
{ author: :avatar }
[ :books, :author ]
:books
ActiveRecord::QueryMethods. So +associations+ could look like this:
+:associations+ has the same format as the +:include+ method in
book's author, as well as that author's avatar.
example, specifying { author: :avatar } will preload a
association names for the to-be-preloaded association objects. For
- a Hash which specifies multiple association names, as well as
books.
allows this method to preload an author's avatar as well as all of his
is processed recursively. For example, specifying [:avatar, :books]
- an Array which specifies multiple association names. This array
for an Author.
example, specifying +:books+ allows this method to preload all books
- a Symbol or a String which specifies a single association name. For
preload. It may be:
+associations+ specifies one or more associations that you want to
flattening +records+.
+preload_associations+ will preload all associations records by
i.e. +records+ itself may also contain arrays of records. In any case,
+records+ is an array of ActiveRecord::Base. This array needs not be flat,
== Parameters
names +:author+ and +:buyers+.
belongs_to :author, has_many :buyers has association
to an association creation method. For example, a model that specifies
In this description, 'association name' shall refer to the name passed
Eager loads the named associations for the given Active Record record(s).
def initialize(records:, associations:, scope: nil, available_records: [], associate_by_default: true) @records = records @associations = associations @scope = scope @available_records = available_records || [] @associate_by_default = associate_by_default @tree = Branch.new( parent: nil, association: nil, children: @associations, associate_by_default: @associate_by_default, scope: @scope ) @tree.preloaded_records = @records end
def loaders
def loaders branches.flat_map(&:loaders) end