module ArLazyPreload::Relation

ActiveRecord::Relation patch with lazy preloading support

Attributes

lazy_preload_values[W]
preloads_associations_lazily[W]

Public Instance Methods

lazy_preload(*args) click to toggle source

Specify relationships to be loaded lazily when association is loaded for the first time. For example:

users = User.lazy_preload(:posts)
users.each do |user|
  user.first_name
end

will cause only one SQL request to load users, while

users = User.lazy_preload(:posts)
users.each do |user|
  user.posts.map(&:id)
end

will make an additional query.

# File lib/ar_lazy_preload/active_record/relation.rb, line 68
def lazy_preload(*args)
  check_if_method_has_arguments!(:lazy_preload, args)
  spawn.lazy_preload!(*args)
end
lazy_preload!(*args) click to toggle source
# File lib/ar_lazy_preload/active_record/relation.rb, line 73
def lazy_preload!(*args)
  args.flatten!
  self.lazy_preload_values += args
  self
end
lazy_preload_values() click to toggle source
# File lib/ar_lazy_preload/active_record/relation.rb, line 79
def lazy_preload_values
  @lazy_preload_values ||= []
end
load() click to toggle source

Enhanced load method will check if association has not been loaded yet and add a context for lazy preloading to loaded each record

Calls superclass method
# File lib/ar_lazy_preload/active_record/relation.rb, line 27
def load
  need_context = !loaded?
  result = super
  if need_context
    Context.register(
      records: ar_lazy_preload_records,
      association_tree: lazy_preload_values,
      auto_preload: preloads_associations_lazily?
    )
  end
  result
end
preload_associations(records) click to toggle source
# File lib/ar_lazy_preload/active_record/relation.rb, line 12
def preload_associations(records)
  preload = preload_values
  preload += includes_values unless eager_loading?
  preloader = nil
  preload.each do |associations|
    preloader ||= build_preloader
    preloader_associations = preloader.preload records, associations
    preloader_associations.each do |preloader_association|
      handle_preloaded_records(preloader_association.preloaded_records)
    end
  end
end
preload_associations_lazily() click to toggle source

Lazily autoloads all associations. For example:

users = User.preload_associations_lazily
users.each do |user|
  user.posts.flat_map {|post| post.comments.map(&:id)}
end

Same effect can be achieved by User.lazy_preload(posts: :comments)

# File lib/ar_lazy_preload/active_record/relation.rb, line 48
def preload_associations_lazily
  spawn.tap { |relation| relation.preloads_associations_lazily = true }
end

Private Instance Methods

ar_lazy_preload_records() click to toggle source
# File lib/ar_lazy_preload/active_record/relation.rb, line 85
def ar_lazy_preload_records
  @records
end
handle_preloaded_records(preloaded_records) click to toggle source
# File lib/ar_lazy_preload/active_record/relation.rb, line 93
def handle_preloaded_records(preloaded_records)
  return unless Contexts::TemporaryPreloadConfig.enabled? || preloads_associations_lazily?

  records_array = PreloadedRecordsConverter.call(preloaded_records)

  return unless records_array&.any?

  Context.register(
    records: records_array,
    association_tree: lazy_preload_values,
    auto_preload: true
  )
end
preloads_associations_lazily?() click to toggle source
# File lib/ar_lazy_preload/active_record/relation.rb, line 89
def preloads_associations_lazily?
  @preloads_associations_lazily ||= false
end