是否可以使用宏为所有 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 的普通函数。
我希望我的所有模型都可以访问 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 的普通函数。