无法通过__using__宏Elixir / Phoenix了解导入

时间:2015-12-26 12:59:49

标签: elixir phoenix-framework

我正在使用Phoenix框架并尝试创建一个用于身份验证的插件,但我遇到了错误。以下是此示例代码。

defmodule ChhutiServer.GoogleAuthController do
  use ChhutiServer.Web, :controller
  use ChhutiServer.Plugs.GoogleAuth
end

# inside lib/plugs

defmodule ChhutiServer.Plugs.GoogleAuth do
  import Plug.Conn

  defmodule ChhutiServer.Behaviour.GoogleAuth do
    @callback callback(Plug.Conn.t, map) :: any
  end

  defmacro __using__(_) do
    quote do
      plug ChhutiServer.Plugs.GoogleAuth
    end
  end
end

以上代码返回错误。

== Compilation error on file web/controllers/google_auth_controller.ex ==
** (UndefinedFunctionError) undefined function: ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Plugs.GoogleAuth.init/1 (module ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Plugs.GoogleAuth is not available)
    ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Plugs.GoogleAuth.init([])
    (plug) lib/plug/builder.ex:198: Plug.Builder.init_module_plug/3
    (plug) lib/plug/builder.ex:186: anonymous fn/4 in Plug.Builder.compile/3
    (elixir) lib/enum.ex:1387: Enum."-reduce/3-lists^foldl/2-0-"/3
    (plug) lib/plug/builder.ex:186: Plug.Builder.compile/3
    (phoenix) expanding macro: Phoenix.Controller.Pipeline.__before_compile__/1
    web/controllers/google_auth_controller.ex:1: ChhutiServer.GoogleAuthController (module)
    (elixir) lib/kernel/parallel_compiler.ex:100: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/8

我理解错误是由于elixir正在寻找ChhutiServe.Plugs.GoogleAuth.ChhuttiServer.Plugs.GoogleAuth而不是ChhuttiServe.Plugs.GoogleAuth。我也可以通过在Elixir命名空间前面添加模块来解决这个问题。即使用以下代码。

defmodule ChhutiServer.Plugs.GoogleAuth do
  import Plug.Conn

  defmodule ChhutiServer.Behaviour.GoogleAuth do
    @callback callback(Plug.Conn.t, map) :: any
  end

  defmacro __using__(_) do
    quote do
      plug Elixir.ChhutiServer.Plugs.GoogleAuth
    end
  end
end

但我无法理解为什么它的表现如此。任何人都可以帮助我。

更新

添加所有必需的代码。

1 个答案:

答案 0 :(得分:4)

问题是因为我在ChhutiServer.Behaviour.GoogleAuth内声明了模块ChhutiServer.Plugs.GoogleAuth。但报道的错误非常奇怪且具有误导性。将ChhutiServer.Behaviour.GoogleAuth移到ChhutiServer.Behaviour.GoogleAuth之外会解决问题。我注意到的另一个奇怪的事情是在ChhutiServer.Behaviour.GoogleAuth宏下移动__using__也会删除错误。由于错误报告看起来很奇怪,我提出了一个问题。以下是问题

https://github.com/elixir-lang/elixir/issues/4120

<强>更新

Jose Valim帮助我理解这不是问题,而是由于我们定义嵌套模块时产生的别名。我将解释为什么我们遇到上述错误,以便面临类似问题的其他人可以理解这个概念。

每当我们在elixir中创建嵌套模块时,我们都会为嵌套模块创建别名。以下是示例。

defmodule A do
  defmodule B do
  end
end

以上代码相当于

defmodule A.B do
end

defmodule A do
  alias A.B, as: B
end

因此嵌套模块创建别名,以便可以在父模块内访问它而没有完全限定名称,即我们可以直接在A中使用B代替A.B来访问B.

现在让我们回答我的问题并检查最新情况并了解产生的错误。

defmodule ChhutiServer.Plugs.GoogleAuth do
  import Plug.Conn


  defmodule ChhutiServer.Behaviour.GoogleAuth do
    @callback callback(Plug.Conn.t, map) :: any
  end


  defmacro __using__(_) do
    quote do
      plug ChhutiServer.Plugs.GoogleAuth
    end
  end
end

在上面的代码模块中,ChhutiServer.Behaviour.GoogleAuth嵌套在ChhutiServer.Plugs.GoogleAuth中。因此ChhutiServer.Behaviour.GoogleAuth的完全限定名称为ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Behaviour.GoogleAuth。正如我们所见,嵌套模块创建了别名,上面的代码与

相同
  defmodule ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Behaviour.GoogleAuth do
    @callback callback(Plug.Conn.t, map) :: any
  end

  defmodule ChhutiServer.Plugs.GoogleAuth do
    import Plug.Conn
    alias ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Behaviour.GoogleAuth, as: ChhutiServer.Behaviour.GoogleAuth   

    defmacro __using__(_) do
     quote do
       plug ChhutiServer.Plugs.GoogleAuth
     end
    end
  end

由于此别名现在ChhutiServer指向ChhutiServer.Plugs.GoogleAuth.ChhutiServer,因此我们可以在模块内使用ChhutiServer.Behaviour.GoogleAuth ChhutiServer.Plugs.GoogleAuth而不是使用完整限定名ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Behaviour.GoogleAuth。然后当我们使用 在plug ChhutiServer.Plugs.GoogleAuth宏内__using__,它会像现在一样扩展到plug ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Plugs.GoogleAuth ChhutiServer指向ChhutiServer.Plugs.GoogleAuth.ChhutiServer。由于elixir无法找到模块,因此出现了错误 ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Plugs.GoogleAuth

我还指出,当我们在ChhutiServer.Behaviour.GoogleAuth宏下面定义模块__using__时,上面的代码运行时没有任何错误。 这是因为别名在elixir中的词法作用域。因此,如果我们将ChhutiServer.Behaviour.GoogleAuth置于__using__宏之下,则会在宏下方定义别名。 所以plug ChhutiServer.Plugs.GoogleAuth没有扩展到任何东西,它仍然保持不变。 elixir能够找到ChhutiServer.Plugs.GoogleAuth。解释上述事情的小例子。

defmodule A.B做   def b do   结束 端

defmodule A do   做一个     #将返回错误&#34;模块B不可用&#34;因为A.B还没有别名。     #如果我们想致电b,我们必须将其写为A.B.b     B.b   结束   别名A.B,如:B 端

如果我们在使用B之前使用别名,它将起作用。

defmodule A do   别名A.B,如:B
  做一个     B.b   结束 端

希望这有助于其他人了解嵌套模块和别名。感谢Jose Valim的解释

参考文献:

http://elixir-lang.org/getting-started/alias-require-and-import.html

https://github.com/elixir-lang/elixir/issues/4120