Includes 方法( n+ 1 issue )不适用于 push 方法,但在分配给数组时与 += 一起使用

Includes method ( n+ 1 issue ) doesn't work with a push method but does with += when assigning to an array

考虑 Employee 和 Company 模型之间的简单关系(多对多):

公司型号:

has_many :employees, through: :company_employees
has_many :company_employees

员工模型:

has_many :companies, through: :company_employees
has_many :company_employees

公司员工模型(加入table):

belongs_to :employee
belongs_to :company

也是所有者模型:

has_many :companies

所以在我的系统中,我有一个可能拥有多家公司的所有者和一个可能为多家公司工作的员工。

现在,在我的员工 控制器 中,我想获取为所有者工作的所有员工:

def owners_linked
 @company_employees = []
 owner.companies.each do |company|
   @company_employees.push (company.company_employees.includes(:company, :employee)) # when += instead of push - it works
 end
 respond_to do |format|
   format.js {render "employees_list"}
 end
end

我需要访问 Employee 实例(个人数据),company_employees table(公司职位信息)和 Company(公司相关数据)。
为了解决 n+1 问题 并提高性能,我使用了 includes 方法。 好吧,问题是在我的控制器动作中:

@company_employees.push company.company_employees.includes(:company, :employee)

当使用 push 方法时,它不起作用。我在未定义员工方法的视图中获得错误。 另一方面,当我将推送更改为 += 签名时,它工作得很好。

谁能帮我理解为什么会这样?
我知道 += 是无效的,所以我不想坚持下去。

我通常从另一边进来攻击这个。我相信这会得到你正在寻找的东西:

@company_workers = Employee.where(company_id: owner.companies.pluck(:id))

where(company_id: ...) 可以获取一个数组并自动将其设置为 SQL 中的 in(...) 命令。

所以 SQL 最终会变成这样:

select * from employees where company_id is in(1,2,3,4)

1、2、3、4 是所有者的公司 ID。

这与您使用 include 没有任何关系。

当您使用 += 时,您最终会得到一个包含 CompanyEmployee 个对象的数组。但是,当您使用 push 时,您不再连接数组,而是创建一个集合数组。然后,您在集合上调用 employee 而不是集合的元素,这就是您收到错误的原因。

我个人会这样写

@company_employees = owner.companies.flat_map do |company|
  company.companee_employees.include(...)
end

尽管出于简洁而不是性能的原因,我会这样做。与从数据库中获取数据所花费的时间相比,+= 和其他连接数组的方法之间的任何性能差异都是微不足道的。

但这并不能完全解决您的 n+1 问题,因为每个公司的数据都是单独加载的。我愿意

@company_employees = owner.companies.include(company_employees: [:company, :employee]).flat_map(&:company_employees)

它没有执行那么多查询。