Ecto join with dynamically built conditions

I think the answer is to use the dynamic function.

This works. (leaving out the some_var condition I had earlier).

def posts_with_comments(approved, featured) do
  query = Post
  join(query, :left, [p], c in Comment, ^do_join(approved, featured))
  |> preload([p, c], [comments: c])
  |> Repo.all
end

defp do_join(approved, featured) do
  dynamic = dynamic([p, c], c.post_id == p.id)

  dynamic =
  case approved do
    nil -> dynamic
    _ -> dynamic([p, c], ^dynamic and c.approved == ^approved)
  end

  case featured do
    nil -> dynamic
    _ -> dynamic([p, c], ^dynamic and c.featured == ^featured)
  end
end

That's much better than my first attempt because it's a simple concatenation that just gets longer with more conditions rather than an explosion of conditions.

As an exercise I have been unable to make this more generic by feeding it a list of fields and using something like reduce. The problem I had there was making the field name (e.g., c.approved) work from a variable.

join seems to support two types of on parameters. The keyword list (which I assume implies ==) and the more expressive format. dynamic does not seem to work with the keyword list. It tries to expand p.id to p.id().

I couldn't get @mudasobwa's macro based solutions to work. I'm not exactly a macro expert yet but I don't see how the nil match can work at run time.

One more thing about the macro solution. For some reason, it doesn't work with the keyword list either. I would expect a bare bones macro like this to work:

defmacrop do_join do
  quote do
    [post_id: p.id]
  end
end

But it doesn't. It tries to expand p.id to p.id()

Tags:

Elixir

Ecto