所以现在我正在使用它:
dynamic(
[u],ilike(
u.name,^"%#{String.replace(term,"%","\\%")}%"
)
)
该术语是一个简单的字符串,例如"charlie"
。我如何将其与一系列术语结合使用,例如:["charlie","dennis","frank"]
-甚至没有片段也可能吗?
所以现在我正在使用它:
dynamic(
[u],ilike(
u.name,^"%#{String.replace(term,"%","\\%")}%"
)
)
该术语是一个简单的字符串,例如"charlie"
。我如何将其与一系列术语结合使用,例如:["charlie","dennis","frank"]
-甚至没有片段也可能吗?
通过一些元编程,在某种程度上这是可能的。幸运的是,ecto在查询中允许使用您自己的宏,因此我们所需要的只是构建自己的宏,将多个ILIKE
与or
粘合在一起。
这里有几个陷阱。首先,我们应该让编译器忘记Kernel.or/2
,否则,它将试图就地进行逻辑分离。然后,我们应该显式导入Ecto.Query.API.or/2
。最后,我们应该以递归方式构建最终的AST,以传递给查询。
总结。
defmodule MultiOr do
import Ecto.Query
import Kernel,except: [or: 2]
import Ecto.Query.API,only: [or: 2]
# terminating call,we are done
defp or_ilike(var,[term1,term2] = terms) do
quote do
ilike(unquote(var),unquote(term1)) or ilike(unquote(var),unquote(term2))
end
end
# recursive call
defp or_ilike(var,[term | terms]) do
quote do
ilike(unquote(var),unquote(term)) or unquote(or_ilike(var,terms))
end
end
# the macro wrapper to inject AST
defmacrop multi_ilike(var,terms) do
Macro.expand(or_ilike(var,terms),__CALLER__)
end
# test
def test do
Ecto.Query.where(User,[u],multi_ilike(u.name,["charlie","dennis","frank"]))
end
end
请注意,or_ilike/2
是功能,返回AST。它们不能是宏(可能会简化一切),因为不能递归调用宏(应在第一次调用之前预先定义宏)。
让我们看看。
MultiOr.test
#⇒ #Ecto.Query<from u0 in User,# where: ilike(u0.name,"charlie") or (ilike(u0.name,"dennis") or ilike(u0.name,"frank"))>
,
您可以使用标准Ecto中的or_where
:
def multi_ilike(queryable,words) do
for term <- words,reduce: queryable do
acc ->
str = "%#{String.replace(term,"%","\\%")}%"
cond = dynamic([u],ilike(u.name,^str))
or_where(acc,^cond)
end
end
或者如果您不喜欢for
:
def multi_ilike(queryable,words) do
words
|> Enum.map(&"%#{String.replace(&1,"\\%")}%")
|> Enum.map(&dynamic([u],^&1))
|> Enum.reduce(queryable,&or_where(&2,^&1))
end