Доступ к связанным записям при использовании пользовательского объединения на coalesce-d id - PullRequest
0 голосов
/ 07 октября 2019

В моем приложении Rails есть две модели: Organization и Warehouse. Записи в Warehouse принадлежат записям в Organization через Warehouse.organization_id («собственные» склады) или Warehouse.delegated_to_organization_id («делегированные» склады).

В случае использования я должен пройти через все складыи их «ответственная» организация: «делегирующая» организация, если она существует, в противном случае - «владеющая» организация. Однако в случаях использования требуется, чтобы все склады были сгруппированы по организации и выполнялись партиями (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.

Что я делаю не так и как я могу заставить эту работу работать?


(1) Объем данных слишком велик для чтения всего в память, поэтому фактический запрос выполняется в пакетном режиме (с присоединенным limit).

1 Ответ

1 голос
/ 07 октября 2019

organizations.first.warehouses будет извлекать warehouses только на organization_id. Ваш 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 невозможно (или не так просто) использовать includes с пользовательским условием JOIN.

Но в вашем случае я бы не стал этого делать, есть другой способ. Используйте две отдельные ассоциации (по одной для каждого внешнего ключа):

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

...