Reputation: 838
I am using Phoenix framework and trying to create a plug for authentication, but am stuck with an error. Below is this sample code.
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
Above code returns below error.
== 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
I understand the error is due to elixir looking for ChhutiServe.Plugs.GoogleAuth.ChhuttiServer.Plugs.GoogleAuth
instead of ChhuttiServe.Plugs.GoogleAuth
. And I am also able to solve this issue by prepending modules with Elixir namespace. ie by using below code.
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
But I am not able to understand why its behaving like this. Can anyone please help me out.
UPDATE
Add all required code.
Upvotes: 4
Views: 908
Reputation: 838
Issue was because I have declared module ChhutiServer.Behaviour.GoogleAuth
inside ChhutiServer.Plugs.GoogleAuth
. But error reported is very strange and misleading. Moving ChhutiServer.Behaviour.GoogleAuth
outside ChhutiServer.Behaviour.GoogleAuth
solves the issue. One more strange thing that I noticed is moving ChhutiServer.Behaviour.GoogleAuth
below the __using__
macro also removes the error. As error report looks strange , I raised an issue. Below is the issue
https://github.com/elixir-lang/elixir/issues/4120
Update
Jose Valim helped me to understand that it is not an issue and its due to alias created when we defined nested module. I will explain why we get above error so that other people facing similar issue may understand the concept.
Whenever we create a nested module in elixir we are creating aliase for the nested module. Below is the example.
defmodule A do
defmodule B do
end
end
Above code is equivalent to
defmodule A.B do
end
defmodule A do
alias A.B, as: B
end
So nested module creates the alias so that it can be accessed inside the parent module without fully-qualified name i.e we can use B
instead of A.B
directly inside A to access B.
Now lets come to my question and check whats going on and understand the error generated.
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
In above code module ChhutiServer.Behaviour.GoogleAuth
is nested within ChhutiServer.Plugs.GoogleAuth
. So fully-qualified name of ChhutiServer.Behaviour.GoogleAuth
is ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Behaviour.GoogleAuth
. As we have seen nested module creates the alias, above code is same as
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
Due to this alias now ChhutiServer
points to ChhutiServer.Plugs.GoogleAuth.ChhutiServer
so that we can use ChhutiServer.Behaviour.GoogleAuth
inside module
ChhutiServer.Plugs.GoogleAuth
instead of using full qualified name ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Behaviour.GoogleAuth
. Then when we use
plug ChhutiServer.Plugs.GoogleAuth
inside __using__
macro it gets expanded to plug ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Plugs.GoogleAuth
as now
ChhutiServer
points to ChhutiServer.Plugs.GoogleAuth.ChhutiServer
. And there comes our error as elixir is not able to find the module
ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Plugs.GoogleAuth
Also I pointed out in question that when we define module ChhutiServer.Behaviour.GoogleAuth
below __using__
macro then the above code runs without any error.
This is because alias are lexically scoped in elixir. So if we bring ChhutiServer.Behaviour.GoogleAuth
below __using__
macro the alias is defined below the macro.
So plug ChhutiServer.Plugs.GoogleAuth
is not expanded to anything and it remains as it is. And elixir is able to find ChhutiServer.Plugs.GoogleAuth
. Small example to explain above thing.
defmodule A.B do def b do end end
defmodule A do
def a do
# will return error "module B is not available" as A.B is not aliased yet.
# If we want to call b
we have to write it as A.B.b
B.b
end
alias A.B, as: B
end
If we use alias before using B
it will work.
defmodule A do
alias A.B, as: B
def a do
B.b
end
end
Hope this helps other to understand nested module and alias. Thanks Jose Valim for the explanation
References:
http://elixir-lang.org/getting-started/alias-require-and-import.html
https://github.com/elixir-lang/elixir/issues/4120
Upvotes: 5