【问题标题】:Elixir + Phoenix : __MODULE__ undefined inside a quoteElixir + Phoenix:__MODULE__ undefined 在引号内
【发布时间】:2017-01-02 17:13:19
【问题描述】:

(代码引用已匿名)

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

  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

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

非常感谢这里的一些灯,谢谢。

【问题讨论】:

  • 你能把你use这个模块所在的模块的代码贴出来吗?
  • 您可能应该在MyModel 模块中交换use MyApp.Helpers.Modeldefstruct MyModel 行,让后者(结构声明)先行。
  • 好的,按照@mudasobwa 的建议,我将use MyApp.Helpers.Model.Changeset 放在schema 调用之后,该调用在MyModel 中生成结构,它可以工作。所以我想没有办法把这个use全局放在凤凰通常的MyApp.Helpers, :model中,因为我们需要先定义模式?
  • @Dogbert 我在问题中添加了MyModel 定义,谢谢。
  • 您可以使用@after_compile 回调来实现此功能。

标签: elixir phoenix-framework ecto


【解决方案1】:

问题是该结构是通过调用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 的文档中所述

最后,开发人员还应该避免在 __using__/1 回调,除非这些函数是先前定义的 @callback 的默认实现或者是函数 意味着被覆盖(请参阅defoverridable/1。即使在这些情况下, 定义函数应该被视为“最后的资源”。

__using__ 中定义函数有很多缺点,包括:编译速度慢(函数在注入的每个模块中都反复编译)、调试困难(“这是从哪里来的?”)和可堆肥性差。

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

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

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

【讨论】:

  • 感谢您提供如此详细的答案!
猜你喜欢
  • 2016-12-05
  • 2018-05-22
  • 1970-01-01
  • 1970-01-01
  • 2017-02-05
  • 2020-06-21
  • 2015-07-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多