来自单独模块的Ecto片段宏,无需导入

问题描述 投票:0回答:1

如果我们检查在official docs中使用Ecto片段的示例,它会说为了使用来自另一个模块的宏,我们需要先导入该模块。所以,如果我们有一个带有coalesce/2宏的模块:

defmodule CustomFunctions do
  defmacro coalesce(left, right) do
    quote do
      fragment("coalesce(?, ?)", unquote(left), unquote(right))
    end
  end
end

我们需要在另一个模块中导入它以使用它:

import CustomFunctions

之后,我们可以像这样编写查询:

where(Post, [p], p.id in coalesce(3,5)

只要没有名称冲突,它就能很好地工作。但是,让我们说,由于某种原因,我正在创建一些模块,只会导出一个名为query的宏,显然在名称冲突开始出现之前不会花很长时间。所以我想知道是否可以使用其全名来使用上面的宏,如下所示:

require CustomFunctions
where(Post, [p], p.id in CustomFunctions.coalesce(3,5)

显然我尝试了它但它失败了,说CustomFunctions.coalesce(3,5)不是一个正确的查询元素。

那么,是否有一种简单的方法来实现这样的目标?

elixir ecto
1个回答
2
投票

我不知道这个功能是否开箱即用,但您可能会执行以下操作:

defmodule(Macro1, do: defmacro(coalesce(foo), do: quote(do: unquote(foo) + 1)))
defmodule(Macro2, do: defmacro(coalesce(foo), do: quote(do: unquote(foo) - 1)))

这些是具有宏名称冲突的模块。我们将它们导入为macro1_coalescemacro2_coalesce

defmodule EctoExtention do
  defmacro __using__(opts) do
    Enum.flat_map(opts, fn what ->
      {mod, funs} =
        case what do
          {mod, :*} -> {mod, Module.concat([mod]).__info__(:macros)}
          {mod, funs} -> {mod, funs}
        end

      prefix =
        mod
        |> to_string()
        |> String.downcase()

      mod = Module.concat([mod]) # or :"Elixir.#{mod}"

      [
        quote(do: require(unquote(mod)))
        | Enum.map(funs, fn {fun, arity} ->
            args = for i <- 0..arity, i > 0, do: Macro.var(:"arg_#{i}", nil)

            quote do
              @doc ~s"""
              Macro #{unquote(fun)} imported from module #{unquote(mod)}
              """
              defmacro unquote(:"#{prefix}_#{fun}")(unquote_splicing(args)),
                do: unquote(mod).unquote(fun)(unquote_splicing(args))
            end
          end)
      ]
    end)
  end
end

现在让我们定义从不同模块导入宏的规则。

defmodule AllMyMacros do
  use EctoExtention, Macro1: [coalesce: 1], Macro2: :*
end

:*表示导入所有宏。我们来测试一下:

defmodule Test do
  import AllMyMacros
  def info, do: Using.__info__(:macros)
  def test, do: {macro1_coalesce(42), macro2_coalesce(42)}
end


IO.inspect(Test.info(), label: "macros")
#⇒ macros: [macro1_coalesce: 1, macro2_coalesce: 1]

IO.inspect(Test.test(), label: "test")
#⇒ test: {43, 41}
© www.soinside.com 2019 - 2024. All rights reserved.