Elixir + Phoenix : __MODULE__ undefined inside a quote

Elixir + Phoenix : __MODULE__ undefined inside a quote

(代码引用已匿名化)

在我的 phoenix 模型中,我有一些多余的方法,比如这个基本方法:

  def build(params) do
    changeset(%__MODULE__{}, params)
  end

因为我将它们放在我的模型模块中,所以它们工作正常,但我想避免代码重复,并且我想通过这样的辅助模块使它们对我的所有模型可用:

defmodule MyApp.Helpers.Model do
  defmodule Changeset do
    defmacro __using__(_opts) do
      quote do
        def build(params) do
          changeset(%__MODULE__{}, params)
        end
      end
    end
  end
end

执行此操作时出现错误:

== Compilation error on file lib/my_app/model/my_model.ex ==
** (CompileError) lib/my_app/model/my_model.ex:3: MyApp.Model.MyModel.__struct__/1 is undefined, cannot expand struct MyApp.Model.MyModel
    (stdlib) lists.erl:1354: :lists.mapfoldl/3

相关模型基本是这样的:

defmodule MyApp.Model.MyModel do
  use MyApp.Helpers, :model
  use MyApp.Helpers.Model.Changeset # here for comprehension, should be in MyApp.Helpers quoted :model method

  schema "my_table" do
    field :name, :string

    timestamps()
  end

  @required_fields ~w(name)a
  @optional_fields ~w()
  @derive {Poison.Encoder, only: [:name]}

  def changeset(model, params \ %{}) do
    model
    |> cast(params, @required_fields)
    |> cast(params, @optional_fields)
    |> validate_required(@required_fields)
    |> validate_format(:name, ~r/^[a-z]{3,}$/)
    |> unique_constraint(:name)
  end
end

我认为这是因为该模块在编译时尚未在宏中定义,但我不确定,也不确定如何解决此问题并使其正常工作。

这里有些灯会非常感谢,谢谢。

问题是结构是通过调用defstruct宏定义的,不能提前使用,因为编译器不知道如何扩展它。在 ecto 模式的情况下,结构由下面的 schema 宏声明。

幸运的是,查看 defstruct 的文档我们可以看到它在声明结构的模块上创建了一个名为 __struct__/0 的函数。并且函数可以调用其他局部函数,甚至在它们被定义之前!使用这些知识,我们可以将您的宏更改为:

defmodule MyApp.Helpers.Model do
  defmodule Changeset do
    defmacro __using__(_opts) do
      quote do
        def build(params) do
          changeset(__struct__(), params)
        end
      end
    end
  end
end

也就是说,在 __using__ 中定义函数通常被认为是一种不好的做法,如 Kernel.use/1

的文档中所述

Finally, developers should also avoid defining functions inside the __using__/1 callback, unless those functions are the default implementation of a previously defined @callback or are functions meant to be overridden (see defoverridable/1. Even in these cases, defining functions should be seen as a “last resource”.

__using__ 中定义函数有很多缺点,包括:编译速度慢(函数在它注入的每个模块中都被反复编译)、调试困难 ("where is this coming from?") 和可堆肥性差。

更好的方法可能是定义一个单一的、可重用的函数。例如:

defmodule MyApp.SchemaUtils do
  def build(schema, params) do
    schema.changeset(struct(schema), params)
  end
end

PS。 @derive 调用必须在结构之前声明。