根据 Phoenix 文档:
insert(Ecto.Schema.t | Ecto.Changeset.t, Keyword.t) :: {:ok, Ecto.Schema.t} | {:error, Ecto.Changeset.t}
Inserts a model or a changeset.
In case a model is given, the model is converted into a changeset with all model non-virtual fields as part of the changeset. This conversion is done by calling Ecto.Changeset.change/2 directly.
In case a changeset is given, the changes in the changeset are merged with the model fields, and all of them are sent to the database.
If any before_insert or after_insert callback is registered in the given model, they will be invoked with the changeset.
It returns {:ok, model} if the model has been successfully inserted or {:error, changeset} if there was a validation or a known constraint error.
我做了什么
defmodule Dollar.User do
use Dollar.Web, :model
schema "users" do
field :username, :string
field :sms_number, :string
field :email, :string
timestamps
end
@required_fields ~w(sms_number)
@optional_fields ~w(username email)
@doc """
Creates a changeset based on the `model` and `params`.
If no params are provided, an invalid changeset is returned
with no validation performed.
"""
def changeset(user, params \\ :empty) do
user
|> cast(params, @required_fields, @optional_fields)
|> validate_length(:sms_number, is: 10)
|> unique_constraint(:sms_number)
|> unique_constraint(:username)
end
end
我的预期
Repo.insert(%User{})
=> {:error, ... }
我预计会出现错误,因为 sms_number
是必需的。
我实际上得到了什么
[debug] INSERT INTO "users" ("inserted_at", "updated_at", "email", "sms_number", "username") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [{{2016, 3, 19}, {15, 13, 5, 0}}, {{2016, 3, 19}, {15, 13, 5, 0}}, nil, nil, nil] OK query=1.5ms
%Dollar.User{__meta__: #Ecto.Schema.Metadata<:loaded>, email: nil, id: 7,
inserted_at: #Ecto.DateTime<2016-03-19T15:13:05Z>, sms_number: nil,
updated_at: #Ecto.DateTime<2016-03-19T15:13:05Z>, username: nil}
记录存储在数据库中
环境
- Elixir 1.2.3
- Ecto 1.1.4
- Phoenix 埃克托2.0.1
- Phoenix 1.1.4
我做错了什么?
最佳答案
当您尝试直接插入模型中定义的结构时,根本不会调用 changeset
函数。 %User{}
只是模块中的一个结构体,ecto 不知道该模块中定义的函数的任何信息。您可以删除 changeset
函数,并且 Repo.insert
仍然有效。
changeset
函数是您的自定义验证代码。您粘贴的文档指出调用了另一个函数:Ecto.Changeset.change/2
。它创建一个默认有效的变更集,并且不需要任何字段。
你可以自己尝试一下:
changeset = Ecto.Changeset.change %User{}
changeset.required # []
changeset.valid? # true
new_changeset = User.changeset changeset
new_changeset.required # [:sms_number]
new_changeset.valid? # false
在插入数据库之前始终运行自定义验证,并将 Repo.insert
与变更集而不是原始结构一起使用。在迁移中强制数据库本身具有非空约束的必填字段可能也是一个好主意:
add :username, :string, null: false
关于elixir - 如何设置 Ecto 变更集以防止保存缺少必填字段的模型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36103533/