是否可以使用宏为所有 Ecto 模型创建默认的新功能?

Can a default new function be created for all Ecto models using a macro?

我希望我的所有模型都可以访问 new 便利功能,returns 自定义变更集。

类似于:

def new(attrs) do
  changeset(%__MODULE__{}, attrs)
end

这样,当我需要变更集验证时,我可以调用:

Project.Model.new(%{param1: "param1"})

而不是:

Project.Model.changeset(%Model{}, %{param1: "param1"})

我 运行 遇到的问题是,当我执行以下宏时:

defmodule Project.Model do
  defmacro __using__(_) do
    quote do
      use Ecto.Schema
      import Ecto.Changeset
      def new(attrs) do
        changeset(%__MODULE__{}, attrs)
      end
    end
  end
end

... 它不起作用,因为 Ecto 的 schema "model" do ... end 需要在我的 use Project.Model 语句之前编译,否则我会得到一个错误,基本上说我的模块没有结构定义。

我可以将宏简单地限制为 new 函数,并将它放在使用它的模块中间,但这看起来很混乱。

有什么想法吗?

应要求,完整代码如下:

再一次,所有这一切的目的是让模型 use Project.Model 获得一个方便的函数 new 接受属性并将它们放在 changeset 以便在插入数据库之前验证它们。

我得到的错误,正如一些人所暗示的那样,在我可以在我的宏中使用 __MODULE__ 之前必须扩展 schema,因为该结构还没有被定义。

project/user.ex

defmodule Project.User do
  use Project.Model

  schema "users" do
    field :email, :string
    field :first_name, :string
    field :last_name, :string

    timestamps()
  end

  def changeset(user, attrs) do
    user
    |> cast(attrs, [:email, :first_name, :last_name])
    |> validate_required([:email, :first_name, :last_name])
  end
end

project/model.ex

defmodule Project.Model do
  defmacro __using__(_) do
    quote do
      use Ecto.Schema
      import Ecto.Changeset
      def new(attrs) do
        changeset(%__MODULE__{}, attrs)
      end
    end
  end
end

要在定义之前使用结构,您可以尝试这个技巧:

  def new(attrs) do
    changeset(struct(__MODULE__), attrs)
  end

changeset 是你应该为每个模型定义的东西(并且可能有不同的形状)所以我不确定以一般方式在 new 中使用它是个好主意。

此代码的主要问题是宏到 AST 扩展的顺序。我怀疑我是否理解为什么当您手头有一个普通的旧商品 Ecto.Changeset.change/2 时您会尝试使用注入的 #changeset。只要做:

defmodule Project.Model do
  defmacro __using__(_) do
    quote do
      use Ecto.Schema
      import Ecto.Changeset

      def new(attrs) do
        __MODULE__
        |> struct(%{})
        |> change(attrs)
      end
    end
  end
end

您应该已经准备就绪,因为 Ecto.Changeset.change/2 是一个不会尝试扩展为 AST 的普通函数。