Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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()
@char0n

This comment has been minimized.

Copy link

@char0n char0n commented Dec 23, 2019

You can rather use functional composition when building queries. Observe:

  @doc "Fetches video from RDBMS."
  def get_video(id, opts \\ []) do
    base_query = from(video in Video, where: video.id == ^id)

    base_query
    |> filter_not_deleted()
    |> preload_video_metadata(Keyword.get(opts, :preload_video_metadata))
    |> preload_video_channel(Keyword.get(opts, :preload_video_channel))
    |> preload_video_thumbnails(Keyword.get(opts, :preload_video_thumbnails))
    |> Repo.one()
  end

  defp preload_video_channel(queryable, true) do
    from(video in queryable,
      inner_join: video_channel in VideoChannel,
      on: video_channel.id == video.video_channel_id,
      preload: [video_channel: video_channel]
    )
  end

Wouldn't something like that work better for you? This seems like idiomatic solution recommended by various authors. More details about this approach can be found in book Programming Ecto.

@joshnuss

This comment has been minimized.

Copy link
Owner Author

@joshnuss joshnuss commented Dec 23, 2019

@char0n functional composition works too, Was just trying to make join + assoc + preload into one reusable thing.

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