如何在 Rails 7 中预加载 STI 模型的关联?

问题描述 投票:0回答:2

我的问题

想象一下我有这些模型:

class Absence << ApplicationRecord
  belongs_to :user
end

class Vacation << Absence
  belongs_to :vacation_contingent
end

class Illness << Absence; end

现在我想用

检索所有缺席情况
absences = Absence.where(user: xxx)

并迭代假期安排

vacations = absences.select { |absence| absence.is_a?(Vacation)
vacations.each { |vacation| puts vacation.vacation_contingent.xxx }

现在我有 1 个针对这些缺勤的数据库查询,以及 1 个针对每个 Vacation_contingent -> bad

PS:我使用

Absence.where
而不是
Vacation.where
,因为我想用这些
absences
做其他事情。

我尝试了什么

  1. 当然
Absence.where(user: xxx).includes(:vacation_contingent)
# -> ActiveRecord::AssociationNotFoundError Exception: Association named 'vacation_contingent' was not found`
vacations = Vactions.where(user: xxx).includes(:vacation_contingent)
other_absences = Absence.where(user: xxx).where.not(type: 'Vacation')

但是这个很丑,而且我的数据库查询比我想要的多 1 个,因为我要获取缺勤 2 次。

3.

absences = Absence.where(user: xxx)
vacations = absences.select { |absence| absence.is_a?(Vacation)
preloader = ActiveRecord::Associations::Preloader.new
preloader.preload(vacations, :vacation_contingent)
# -> ArgumentError Exception: missing keywords: :records, :associations
# -> (The initializer changed)
absences = Absence.where(user: xxx)
vacations = absences.select { |absence| absence.is_a?(Vacation)
preloader = ActiveRecord::Associations::Preloader.new(records: vacations, associations: %i[vacation_contingent])
# -> This line does nothing on it's own
preloader.call
# -> This just executes SELECT "vacation_contingents".* FROM "vacation_contingents" vacation.size times
preloader.preload
# -> Does the same as .call
# -> And this doesn't even preload anything. When executing
vacations.first.vacation_contingent
# -> then the database is being asked again
ruby-on-rails rails-activerecord ruby-on-rails-7
2个回答
1
投票

在我看来,解决方案 2 是 ActiveRecord 能做到的最好的解决方案。

如果你只想要一个请求,你可以使用原始 SQL 来完成;类似的东西:

Absence.connection.select_all(%{SELECT * 
                                FROM absences
                                LEFT OUTER JOIN vacation_contingents ON absences.vacation_contingents_id = vacation_contingents.id
                                WHERE absences.user_id = ?", user_xxx.id})

它将返回 ActiveRecord::Result,每个

Absence
各一行,以及
Absence
VacationContingent

的两列

0
投票

我找到了解决办法。

ActiveRecord::Associations::Preloader.new(records: vacations, associations: %i[vacation_contingent])
部分就是窍门。我的错误是我只是在 Rails 控制台内调用了
preloader.call
并尝试输出一些内容。如果我调用
preloader.call; nil
,它就会正常工作并预加载内容。

TL;博士

absences = Absence.where(user: xxx)
vacations = absences.select { |absence| absence.is_a?(Vacation)
ActiveRecord::Associations::Preloader.new(records: vacations, associations: %i[vacation_contingent]).call # add a `; nil` if executed inside the rails console
vacations.each { |vacation| puts vacation.vacation_contingent.xxx }
# => no n+1 queries
© www.soinside.com 2019 - 2024. All rights reserved.