Итак, я пытаюсь получить образец для всех моих моделей экто, имеющих дочерние ассоциации.
В своих контроллерах иногда я хочу загрузить только модель, а иногда я хочу загрузить ее со всеми или некоторыми ассоциациями.
def get_account!(id), do: Repo.get!(Account, id)
У учетной записи есть следующие ассоциации:
has_many :users
belongs_to :company
Что было бы хорошим способом изменить мой get_account! функция, которая дает мне возможность предварительно загрузить ассоциации?
Я не хочу, чтобы этот код предварительной загрузки был в моих контроллерах, мне нравится, когда все мои материалы, связанные с запросами, находятся в самом модуле и не просачиваются в мои контроллеры.
Один из способов сделать это - создать функции with_<association>
в вашем контекстном модуле, например:
def get_account!(id), do: Repo.get!(Account, id)
def with_company(%Account{} = account), do: Repo.preload(account, :company)
def with_users(%Account{} = account), do: Repo.preload(account, :users)
поэтому вы можете использовать их в своем контроллере следующим образом:
def show(conn, %{"id" => id}) do
account =
Context.get_account!(id)
|> Context.with_company
|> Context.with_users
render(conn, "show.html", account: account)
end
и у вас есть полностью загруженная учетная запись, доступная в представлении.
Хорошая вещь в этом подходе заключается в том, что вы можете легко расширить его с помощью более конкретных предварительных загрузок, таких как account |> with_most_active_users(10)
.
Я обычно заимствую подход из того, как создаются области видимости Rails. Ecto query принимает в качестве источника другой запрос. Следует вызывать функции семейства Repo.get
, фактически выполняя запрос к базе данных, в качестве последнего вызова, когда к запросу добавляются все предварительные загрузки и другие условия.
Итак, вы можете ввести простой шаблонный запрос, например:
defp do_get_account_query(id),
do: from(a in Account, where: a.id == ^id)
И используйте его во всех своих запросах с Ecto.Repo.one!/2
:
def get_account!(id, preloads \\ []) do
preloads
Enum.reduce(do_get_account_query(id), fn clause, query ->
Repo.preload(query, clause)
end)
|> Repo.one!()
end
Для более сложных областей действия можно полагаться на универсальную функцию и выводить область действия на ее основе.
Это приводит к нескольким запросам к базе данных (1 основной + количество вызовов
Repo.preload
), потому чтоRepo.get!/2
возвращает объект, а не запрос.