元编程功能和测试

时间:2017-07-27 11:46:21

标签: elixir phoenix-framework

首先,我有一个模块,用于定义用户模型的用户角色和验证:

defmodule ClubHomepage.Web.UserRole do

  ...

  @roles %{
    "administrator": "user with all rights",
    "member": "a registered user",
    "match-editor": "editor of matches and reporter of live match events",
    "news-editor": "author/editor of news",
    "player": "an active sports man/woman",
    "team-editor": "right to edit teams",
    "text-page-editor": "author/editor of static page contents",
    "user-editor": "user administrator"
  }

  @spec defined_roles_keys() :: [String.t]
  def defined_roles_keys do
    Map.keys(@roles)
    |> Enum.map(fn(role) -> Atom.to_string(role) end)
  end

  ...

end

我有一个模块,它定义了从前面定义的用户角色的密钥生成的插件函数:

defmodule ClubHomepage.Web.AuthByRole do

  ...

  @spec plug_function_name(String.t) :: Atom.t
  def plug_function_name(user_role_key) do
    "is_#{String.replace(user_role_key, "-", "_")}"
    |> String.to_atom()
  end

  for user_role_key <- UserRole.defined_roles_keys() do
    function_name = plug_function_name(user_role_key)

    @spec unquote(function_name)(Plug.Conn.t, Keyword.t) :: Boolean
    def unquote(function_name)(conn, _options) do
      has_role(conn, unquote(user_role_key))
    end
  end

  ...

end

问题1:我从for comprehension中提取了一些代码到函数plug_function_name中。但在我收到错误之后:

== Compilation error on file lib/club_homepage/web/commands/auth_by_role.ex ==
** (CompileError) lib/club_homepage/web/commands/auth_by_role.ex:31: undefined function plug_function_name/1

我怎样才能以更好的方式做到这一点,但保持我的代码干燥?

我的意思是在我的测试模块中使用plug_function_name生成相同的函数名称并在测试中调用生成的插件函数:

defmodule ClubHomepage.Web.AuthByRoleTest do
  use ClubHomepage.Web.ConnCase

  alias ClubHomepage.Web.UserRole
  alias ClubHomepage.Web.AuthByRole

  setup do
    conn =
      build_conn()
      |> bypass_through(ClubHomepage.Web.Router, :browser)
      |> get("/")
    {:ok, %{conn: conn}}
  end

  for user_role_key <- UserRole.defined_roles_keys() do
    function_name = AuthByRole.plug_function_name(user_role_key)

    test "#{function_name} halts when no current_user exists", %{conn: conn} do
      conn = AuthByRole.is_administrator(conn, [])
      assert flash_messages_contain?(conn, "You are not authorized to view this page.")
      assert conn.halted
    end

    test "#{function_name} continues when the current_user has the #{user_role_key} role", %{conn: conn} do
      conn =
        conn
        |> assign(:current_user, %ClubHomepage.User{roles: "member #{user_role_key}"})
        |> AuthByRole.is_administrator([])
      refute conn.halted
    end
  end
end

问题2:在生成的测试中,我需要使用AuthByRole.is_administrator(conn, []) function_name AuthByRole.替换对(conn, [])的调用。

我该怎么做?

1 个答案:

答案 0 :(得分:1)

对于第一个问题,您需要在单独的模块中定义要在编译时调用的函数,以便Elixir编译器首先编译该模块,并在主模块时使其可用于主模块正在编译中。这是一个你可以适应的例子:

defmodule M.Helper do
  def function_name(x), do: :"#{x}#{x}"
end

defmodule M do
  import M.Helper

  for x <- ~w(a b c) do
    name = function_name(x)
    def unquote(name)(), do: unquote(name)
  end
end
iex(1)> M.aa
:aa
iex(2)> M.bb
:bb
iex(3)> M.cc
:cc

对于第二个问题,您可以使用AuthByRole.unquote(function_name)(conn, [])。这是一个例子:

defmodule MTest do
  use ExUnit.Case
  import M.Helper

  for x <- ~w(a b c) do
    name = function_name(x)
    test name do
      assert M.unquote(name) == unquote(name)
    end
  end
end
$  m mix test --trace

MTest
  * test aa (1.2ms)
  * test cc (0.00ms)
  * test bb (0.00ms)


Finished in 0.02 seconds
3 tests, 0 failures