Class: ActiveRecord::Associations::Preloader

Inherits:
Object
  • Object
show all
Extended by:
ActiveSupport::Autoload
Defined in:
activerecord/lib/active_record/associations/preloader.rb,
activerecord/lib/active_record/associations/preloader/batch.rb,
activerecord/lib/active_record/associations/preloader/branch.rb,
activerecord/lib/active_record/associations/preloader/association.rb,
activerecord/lib/active_record/associations/preloader/through_association.rb

Overview

Implements the details of eager loading of Active Record associations.

Suppose that you have the following two Active Record models:

class Author < ActiveRecord::Base
  # columns: name, age
  has_many :books
end

class Book < ActiveRecord::Base
  # columns: title, sales, author_id
end

When you load an author with all associated books Active Record will make multiple queries like this:

Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a

=> SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
=> SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)

Active Record saves the ids of the records from the first query to use in the second. Depending on the number of associations involved there can be arbitrarily many SQL queries made.

However, if there is a WHERE clause that spans across tables Active Record will fall back to a slightly more resource-intensive single query:

Author.includes(:books).where(books: {title: 'Illiad'}).to_a
=> SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2,
          `books`.`id`   AS t1_r0, `books`.`title`  AS t1_r1, `books`.`sales` AS t1_r2
   FROM `authors`
   LEFT OUTER JOIN `books` ON `authors`.`id` =  `books`.`author_id`
   WHERE `books`.`title` = 'Illiad'

This could result in many rows that contain redundant data and it performs poorly at scale and is therefore only used when necessary.

Defined Under Namespace

Classes: Association, Batch, Branch, ThroughAssociation

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from ActiveSupport::Autoload

autoload, autoload_at, autoload_under, autoloads, eager_autoload, eager_load!, extended

Constructor Details

#initialize(associate_by_default: true, **kwargs) ⇒ Preloader

Eager loads the named associations for the given Active Record record(s).

In this description, 'association name' shall refer to the name passed to an association creation method. For example, a model that specifies belongs_to :author, has_many :buyers has association names :author and :buyers.

Parameters

records is an array of ActiveRecord::Base. This array needs not be flat, i.e. records itself may also contain arrays of records. In any case, preload_associations will preload all associations records by flattening records.

associations specifies one or more associations that you want to preload. It may be:

  • a Symbol or a String which specifies a single association name. For example, specifying :books allows this method to preload all books for an Author.

  • an Array which specifies multiple association names. This array is processed recursively. For example, specifying [:avatar, :books] allows this method to preload an author's avatar as well as all of his books.

  • a Hash which specifies multiple association names, as well as association names for the to-be-preloaded association objects. For example, specifying { author: :avatar } will preload a book's author, as well as that author's avatar.

:associations has the same format as the :include option for ActiveRecord::Base.find. So associations could look like this:

:books
[ :books, :author ]
{ author: :avatar }
[ :books, { author: :avatar } ]

90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'activerecord/lib/active_record/associations/preloader.rb', line 90

def initialize(associate_by_default: true, **kwargs)
  if kwargs.empty?
    ActiveSupport::Deprecation.warn("Calling `Preloader#initialize` without arguments is deprecated and will be removed in Rails 7.0.")
  else
    @records = kwargs[:records]
    @associations = kwargs[:associations]
    @scope = kwargs[:scope]
    @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
end

Instance Attribute Details

#associate_by_defaultObject (readonly)

Returns the value of attribute associate_by_default


54
55
56
# File 'activerecord/lib/active_record/associations/preloader.rb', line 54

def associate_by_default
  @associate_by_default
end

#associationsObject (readonly)

Returns the value of attribute associations


54
55
56
# File 'activerecord/lib/active_record/associations/preloader.rb', line 54

def associations
  @associations
end

#recordsObject (readonly)

Returns the value of attribute records


54
55
56
# File 'activerecord/lib/active_record/associations/preloader.rb', line 54

def records
  @records
end

#scopeObject (readonly)

Returns the value of attribute scope


54
55
56
# File 'activerecord/lib/active_record/associations/preloader.rb', line 54

def scope
  @scope
end

Instance Method Details

#branchesObject


126
127
128
# File 'activerecord/lib/active_record/associations/preloader.rb', line 126

def branches
  @tree.children
end

#callObject


114
115
116
117
118
# File 'activerecord/lib/active_record/associations/preloader.rb', line 114

def call
  Batch.new([self]).call

  loaders
end

#empty?Boolean

Returns:

  • (Boolean)

110
111
112
# File 'activerecord/lib/active_record/associations/preloader.rb', line 110

def empty?
  associations.nil? || records.length == 0
end

#loadersObject


130
131
132
# File 'activerecord/lib/active_record/associations/preloader.rb', line 130

def loaders
  branches.flat_map(&:loaders)
end

#preload(records, associations, preload_scope = nil) ⇒ Object


120
121
122
123
124
# File 'activerecord/lib/active_record/associations/preloader.rb', line 120

def preload(records, associations, preload_scope = nil)
  ActiveSupport::Deprecation.warn("`preload` is deprecated and will be removed in Rails 7.0. Call `Preloader.new(kwargs).call` instead.")

  Preloader.new(records: records, associations: associations, scope: preload_scope).call
end