在 coalesce-d id 上使用自定义连接时访问关联记录

Access associated records when using custom join on coalesce-d id

我的 Rails 应用程序中有两个模型,OrganizationWarehouseWarehouse 中的条目通过 Warehouse.organization_id('owned' 个仓库)或 Warehouse.delegated_to_organization_id('delegated' 个仓库)属于 Organization 中的条目。

一个用例要求我遍历所有仓库及其 'responsible' 组织:'delegating' 组织(如果存在),否则 'owning' 组织。然而,用例要求我将所有仓库按组织分组并分批执行 (1),导致我采用这种方法:

def organizations
  Organization.joins(warehouse_join)
end

def warehouse_join
  <<~SQL
    INNER JOIN warehouses
    ON coalesce(
      warehouses.delegated_to_organization_id,
      warehouses.organization_id,
      -1
    ) = organizations.id
  SQL
end

但是,ActiveRecord 似乎忽略了连接:organizations.first.warehouses 总是产生 [],而使用 ActiveRecord::Base.connection.execute(organizations.to_sql.gsub('"organizations".*', '*')).to_a 执行查询显示来自 warehouses [=39= 的连接字段].

我哪里做错了,我该如何解决这个问题?


(1)数据量太大无法全部读入内存,所以实际查询是分批执行的(附limit

organizations.first.warehouses 只会通过 organization_id 获取 warehouses。您的 warehouse_join 不会影响此关系。

此外,为了通过查询集合的单个对象获得关联对象而无需额外的数据库查询,您必须使用预加载(预加载)方法,例如 includes.

例如,如果您想获取 organizations 的列表及其 warehouses,您应该这样做:

orgs = organizations.includes(:warehouses)

# now you can access `warehouses` of each individual `organization` without extra DB queries
orgs.first.warehouses
orgs.second.warehouses

据我所知,在 ActiveRecord 中不可能(或不太容易)在自定义 JOIN 条件下使用 includes

但在你的情况下我不会尝试这样做,还有另一种方法。使用两个单独的关联(每个外键一个):

class Organization < ActiveRecord::Model
  # delegated warehouses         
  has_many :delegated_warehouses, class_name: "Warehouse", 
                                  foreign_key: "delegated_to_organization_id" 
  # owned, but not delegated to any other organization
  has_many :owned_not_delegated_warehouses, -> { where(delegated_to_organization_id: nil) }, 
                                            class_name: "Warehouse", 
                                            foreign_key: organization_id                                  

  def warehouses_under_responsibility
    # do not do like this!!! those associations are for preloading only!
    delegated_warehouses + owned_not_delegated_warehouses # wrong!!!

    # the right way
    Warehouse.where("coalesce(delegated_to_organization_id, organization_id, -1) = ?", id)
  end
end

现在您可以使用关联单独预加载(预先加载)warehouses 负责。

orgs = Organization.includes(:delegated_warehouses, :owned_not_delegated_warehouses)

orgs.each do |org|
  # now you can access the associated records separately
  owned_warehouses = org.owned_not_delegated_warehouses
  delegated_warehouses = org.delegated_warehouses

  # or use them all together
  all_warehouses = owned_warehouses + delegated_warehouses
end

注:

这些关联只应在预加载仓库时使用,即当有大量组织集合并且您需要获取它们负责的仓库时。

如果您只有一个组织并且需要获取它负责的仓库,您应该使用另一种方法,如 warehouses_under_responsibility 方法所示。

了解为什么我们在预加载时没有使用相同的方法很重要。那是因为我们不能使 include 与自定义连接条件一起工作,即 coalesce(delegated_to_organization_id, organization_id, -1) = organizations.id