I am trying to implement a macro that makes multi-column comparisons less verbose.
# Draft -- does not work
defmacro equals_parent(parent_name, fields) do
fields
|> Enum.map(fn field ->
{field, quote(do: Ecto.Query.parent_as(unquote(parent_name)), unquote(field))}
end)
end
# Expands to
[
bar: parent_as(:foo).bar,
baz: parent_as(:foo).baz
]
# Usage
from c in Child,
join: p in Parent, as: :foo,
where: equals_parent(:foo, [:bar, :baz])
However, I am getting this error: (Ecto.Query.CompileError) Tuples can only be used in comparisons with literal tuples of the same size
IIUC, I shouldn’t need the ^ caret / interpolation operator because the macro outputs the keyword list at compile time.
I would appreciate if someone helped me understand what’s going on here.
IIUC, I shouldn’t need the ^ caret / interpolation operator because the macro outputs the keyword list at compile time.
I believe the keyword list only works outside of macros. You can’t use it with query expressions. Once you have query expressions, then you need to write or and and, no keyword lists.
I am unsure why would you think this even compiles.
Given they are asking why it doesn’t work, that is not helpful feedback. Expecting things to work when they don’t is part of coding (and vice-versa)!
I am unsure why would you think this even compiles.
The macro compiles just fine. The error is on Ecto’s side.
I believe the keyword list only works with values. Like [foo: 123]. You can’t use it with query expressions. Once you have query expressions, then you need to write or and and, no keyword lists.
Huh, I somehow assumed that it works just like on: [foo: tbl.foo]. My bad!
After all, did the macro (isolated by itself) actually work as expected?
With macros generally you want to complare what the code you want to produce looks like in AST form and then make sure your macro returns the same AST.
What I mean to say is that, once you have a query expression at the root, then Ecto assumes you are not using keyword lists. Otherwise someone may try to write this:
where: [bar: equals_parent(...)]
And it won’t work.
I think this should fix, but I haven’t tried it:
defmacro equals_parent(parent_name, fields) do
fields
|> Enum.map(fn field ->
{field, quote(do: Ecto.Query.parent_as(unquote(parent_name)), unquote(field))}
end)
|> Enum.reduce(quote(do: unquote(&1) and unquote(&2))
end
And it has the benefit that it can now compose with other expressions: where: equals_parent(…) or x.y == z.