Skip to content

Instantly share code, notes, and snippets.

@joshnuss
Last active January 25, 2024 18:59
Show Gist options
  • Save joshnuss/9c68ad2c2649b571dd241693dad6f6f6 to your computer and use it in GitHub Desktop.
Save joshnuss/9c68ad2c2649b571dd241693dad6f6f6 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()
@MMAcode
Copy link

MMAcode commented Dec 9, 2022

Would this work also for nested preloads? If yes, how would that code look like? thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment