Skip to content

Instantly share code, notes, and snippets.

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}]
|>, binding, expr, nil, nil, association, nil, nil, __CALLER__)
|> elem(0)
|>, preload_expr, __CALLER__)
import Ecto.Query
import Preloader
# instead of doing this:
|> 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)
|> preload_join(:customer)
|> preload_join(:lines)
|> Repo.all()

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: == ^id)

    |> 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))

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

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.


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