How do you structure your queries?

For more complicated usecase, pattern that i frequently use is to write composeable query with a decent amount of named join. It produce readable code, with nice behavior of join only when necessary.

It looks like

defmodule UserQuery do
  def has_minimum_rating(query, rating) do
    query = query_require(query, :user_profile)
    from(query, [user_profile: up], up.rating > ^ rating)
  end

  def name_like(query, name) do
    query = query_require(query, :user_profile)
    from(query, [user_profile: up], ilike(up.name, ^"#{name}*")
  end
  ...
  defp query_require(query, identifiers) when is_list(identifiers) do
    Enum.reduce(identifiers, query, fn identifier, q ->
      query_require(q, identifier)
    end)
  end

  defp query_require(query, identifier) do
    if has_named_binding?(query, identifier) do
      query
    else
      implement_query_require(query, identifier)
    end
  end

  defp implement_query_require(query, :user_profile) do
    join(query, :left, [u], up in assoc(u, :user_profile), as: :user_profile)
  end
end

and then used like

User
|> UserQuery.has_minimum_rating(4)
|> UserQuery.name_like("john")
|> Repo.all()
2 Likes