在 Ecto 的 "where" 中调用我自己的谓词

Calling my own predicate in "where" in Ecto

我希望能够在 Ecto 的 "where" 中调用函数或谓词。我正在尝试这个:

items = where(Item, [x], Item.my_predicate?(x))

# items = where(Item, [x], Item.my_predicate?(x) == true)

抛出异常:

`Item.my_predicate?(x)` is not a valid query expression

如何解决?

我觉得不可能。

作为一种方法,您可以创建一个单独的策略模块来实现在记录上或作为查询的一部分处理它的功能:

defmodule CheapItemPolicy do
  alias Ecto.Query
  import Query, only: [where: 3]

  def is_cheap?(%Item{price: price}) do
    price in 100..200
  end

  def is_cheap?(%Query{} = query) do
    query 
    |> where([q], q.price >= 100)
    |> where([q], q.price <= 200)
  end
end

显然这只是一种方式。您可以将所有这些功能放入现有模块或将它们分解为单独的模块或创建具有相同逻辑的数据库功能或处理应用程序级别的检查或其他任何内容。

当然可以,但是你不能使用函数but macros

示例:

defmodule Foo do
  defmacro concat_ws(joiner, columns) do
    params_list = "?" |> List.duplicate(Enum.count(columns)) |> Enum.join(",")

    quote do
      fragment(unquote("concat_ws(?," <> params_list <>" )", joiner, unquote_splicing(columns))
    end
  end
end

items = where(Item, [x], Foo.concat_ws(" ", [x.a, x.b]) == "a b")

没有fragment的示例:

defmodule Foo do
  defmacro eql(a, b) do
    quote do: not (is_nil(a) or is_nil(b)) and a == b 
  end
end

但是宏结果仍然需要是有效的查询 AST,所以没有什么特别的东西可以落在那里。

Lukasz 的回答正是您所需要的。 Ecto 查询是在编译时构建的,所以如果你想使用某些东西作为谓词,你需要宏。

但我想建议您也可以撰写您的查询。这应该会给你这样的想法:

def my_predicate(query, value) do
  from r in query, where: r.some_column == ^value
end

并像这样使用它:

query = from r in MySchema, where: r.another_column == ^another_value

query |> my_predicate(some_value)

基本上,使用可组合函数一点一点地构建您的查询。

PS。我总是使用 "query" 形式进行 Ecto 查询。您还可以使用 "piped" 形式并通过将查询管道化到您的函数中来编写查询。