Программно Preload has_many, через: в Elixir Ecto

Я пытаюсь программно прикрепить предварительную загрузку к запросу для одной из моих моделей, которая имеет отношение has_many, through:.

Мои модули:

defmodule MyApp.Chemical do
  use MyApp.Web, :model

  schema "chemicals" do
    has_many :company_chemicals, MyApp.CompanyChemical
    has_many :companies, through: [:company_chemicals, :companies]

    field :name, :string
  end

  def with_companies(query) do
          from  chem      in query,
     left_join: comp_chem in assoc(chem, :company_chemicals),
          join: company   in assoc(comp_chem, :company),
       preload: [companies: company]
  end

end

defmodule MyApp.Company do
  use MyApp.Web, :model

  schema "companies" do
    has_many :company_chemicals, MyApp.CompanyChemical
    has_many :chemicals, through: [:company_chemicals, :chemicals]

    field :name, :string
  end
end

defmodule MyApp.CompanyChemical do
  use MyApp.Web, :model

  schema "company_chemicals" do
    belongs_to :chemical, MyApp.Chemical
    belongs_to :company, MyApp.Company
  end
end

В этих моделях MyApp.Chemical.with_companies/1 работает как ожидалось, возвращая запрос, который создаст химический элемент с заполненным полем :companies, но я пытался сделать функцию, подобную следующей, для программной предварительной загрузки полей через таблицу ассоциаций:

def preload_association(query, local_assoc, assoc_table, assoc_table_assoc) do
       from  orig in query,
  left_join: association_table in assoc(orig, ^assoc_table),
       join: distal in assoc(association_table, ^assoc_table_assoc),
    preload: [{^local_assoc, distal}]
end

Однако эта функция не будет компилироваться из-за строки preload: [{^local_assoc, distal}].

Как можно предварительно загрузить ассоциированный объект has_many? Спасибо.

Ответ 1

Вы фильтруете свои соединения каким-либо образом? Потому что, если вы этого не сделаете, вместо этого вы должны вызвать предварительную загрузку:

query = from c in MyApp.Company, where: ...
companies = Repo.all(query)
companies_with_chemicals = Repo.preload(companies, :chemicals)

Или:

query = from c in MyApp.Company, preload: :chemicals
companies_with_chemicals = Repo.all(query)

Он будет быстрее, так как он выполняет два отдельных запроса, уменьшая общий размер набора результатов, который будет обрабатываться от companies_size * chemicals_size до companies_size + chemicals_size.

Обратите внимание, что вы также можете присоединиться к has_many :through.