andreaseriksson

andreaseriksson

I have it in the context because I would use it in controllers and liveviews.

1
Post #11
cj5

cj5

Not something that is often recommended (because it is not very explicit and requires model knowledge), but I have been doing something like this for a project (it is an API only project that exposes a GQL API for a SPA).

def list_users(opts \\ []) do 
  preload = Keyword.get(opts, :preload, [])

  User
  |> Repo.base_query(opts)
  |> Repo.all()
  |> Repo.preload(preload)
end

# ...
list_users(where: %{first_name: "John"}, order_by: %{verified_at: :desc}, preload: [:friends])

And then magic happens inside Repo.base_query that sends appends the where and orders to the query. Works well and is very concise considering this has to be done for several different schemas and the orders and where clauses could be very different depending on the GQL API usage.

2
Post #12
tomekowal

tomekowal

I like solutions proposed here but I’d like to suggest one more.
I’ve read an article somewhere that promoted the following idea:
group your functions into module by return type.

E.g. one module returns a User or a list of Users and other module returns Users with Friends.

0
Post #13
OvermindDL1

OvermindDL1

For note, I would go with the composable versions nowadays, I only used my more complicated/powerful builder because back when it was made you couldn’t name a join in Ecto like you can now, they were only positional back then, so I had to build up the joins first then encode the positions into a dispatched code flow since I couldn’t dynamically select, it was a pain, but powerful. Nowadays with named joins it is so much easier!

2
Post #14
csadewa

csadewa

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
Post #15
— All posts loaded —