我正在学习Elixir,并遇到以下情况:我有一个Ecto模式,我想做一个像“ get_by”这样的函数,它带有一个列名,并且它的值是这样的一个参数:get_by(:id, 7)
因此,该功能的工作版本如下:
def get_by(column, value) do
Repo.all(
from(
r in __MODULE__,
where: field(r, ^column) == ^value,
)
)
end
我知道这是功能齐全的,但我想知道field
宏的工作原理。原始代码对我来说太难读了。我试图在宏中使用AST,但似乎没有任何效果。我最好的是:
defmacro magic(var, {:^, _, [{column, _, _}]}) do
dot = {:., [], [var, column]}
{dot, [], []}
end
但是这将返回r.column
而不是绑定到column
变量的原子。应该如何编写宏以返回r.id
?
如果您要检查Ecto.Query.API.field/2
的源代码,您会看到对该函数的显式调用(不是宏btw)raises。
这是因为仅在Ecto.Query.API.field/2
宏内部有意义。
为什么Ecto.Query.from/2
不能单独实现?因为它需要访问列的[[value,由于Ecto.Query.from/2
,它在宏内部不可用。
field/2
不幸的是,该技巧不适用于macro hygiene,因为处理所有上下文可用的整体的原因。defmodule M do defmacro magic({:^, _, [arg]}) do quote do: var!(unquote(arg)) end end require M column = :id M.magic(^column) #⇒ :id
会导致尝试在r.id
上调用unquote(arg1).var!(unquote(arg2))
方法。为了更好地理解上述内容,您可能应该自己弄清楚哪里有可用的AST。在
var!/1
之外,不能调用arg1
绕过卫生,而在(要注射的AST中)限制在岩石上方。这就是quote do
接收整个AST转换为查询and
var!/1
。