Skip to content

Instantly share code, notes, and snippets.

@benbarber
Forked from joshnuss/preloader.exs
Created July 9, 2023 09:25
Show Gist options
  • Save benbarber/acd3967c7322666d2dc684554fe43aee to your computer and use it in GitHub Desktop.
Save benbarber/acd3967c7322666d2dc684554fe43aee to your computer and use it in GitHub Desktop.
Preloading & joining with Ecto, simplified.
# Preloading usually required an extra query.
# To do it in one query, a `join` is needed, and the call to `preload` needs to know the name of join
# This macro does both the `join` and `preload` together
defmodule Preloader do
import Ecto, only: [assoc: 2]
alias Ecto.Query.Builder.{Join, Preload}
defmacro preload_join(query, association) do
expr = quote do: assoc(l, unquote(association))
binding = quote do: [l]
preload_bindings = quote do: [{unquote(association), x}]
preload_expr = quote do: [{unquote(association), x}]
query
|> Join.build(:left, binding, expr, nil, nil, association, nil, nil, __CALLER__)
|> elem(0)
|> Preload.build(preload_bindings, preload_expr, __CALLER__)
end
end
import Ecto.Query
import Preloader
# instead of doing this:
Invoice
|> join(:left, [i], assoc(i, :customer), as: :customer)
|> join(:left, [i], assoc(i, :lines), as: :lines)
|> preload([lines: l, customers: c], lines: l, customer: c)
|> Repo.all()
# you can do this: (exactly the same query)
Invoice
|> preload_join(:customer)
|> preload_join(:lines)
|> Repo.all()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment