Elixir 使用EXTO正确设置检查约束
我的模型中有这个check_约束Elixir 使用EXTO正确设置检查约束,elixir,ecto,Elixir,Ecto,我的模型中有这个check_约束 def changeset(struct, params \\ %{}) do struct |> cast(params, @all_fields) |> validate_required(@required_fields) |> check_constraint(:stars, name: :stars_range, message: "stars must be between 1 and 5")
def changeset(struct, params \\ %{}) do
struct
|> cast(params, @all_fields)
|> validate_required(@required_fields)
|> check_constraint(:stars, name: :stars_range, message: "stars must be between 1 and 5")
end
已成功迁移创建约束
create constraint("reviews", "stars_range", check: "stars>=1 and stars<=5")
引自:
(…)现在,当调用Repo.insert/2或Repo.update/2时,如果价格不是正数,它将转换为一个错误,并由存储库返回{:error,changeset}。请注意,只有在命中数据库之后才会发生错误,因此在所有其他验证通过之前,该错误将不可见
这意味着只有当查询命中数据库时才会发生check\u约束。因此,当您在实际调用数据库之前检查验证时,您的变更集.valid?
将返回true
。您创建的约束是在数据库中创建的,所以在调用它之前,Exto实际上无法知道该约束实际检查了什么。通常,此类约束用于更复杂的检查,或者如果数据库中已经定义了约束(可能是因为您从其他系统迁移了数据库?)。如果希望看到约束的作用,只需在测试中编写:
attrs=%{review_map|stars:7}
变更集=审查。变更集(属性)
{:错误,变更集}=Repo.insert(变更集)
反驳changeset.valid?
如果在调用数据库之前需要Changeset
检查某些条件,则应使用validate\u inclusion/4
或validate\u subset/4
等函数。您甚至可以使用validate\u change/4
编写自己的检查程序(如果需要更多关于如何执行的说明,请告诉我)。如果您使用这些验证器,那么您的变更集将在调用数据库之前工作。如果我在为插入创建变更集时添加一些输出:
defmodule Foo do
alias Foo.Review
require Logger
@repo Foo.Repo
def list_reviews do
@repo.all(Review)
end
def insert_review(attrs) do
changeset = Review.changeset(%Review{}, attrs)
## HERE ###
Logger.debug("changeset.valid? => #{changeset.valid?}")
@repo.insert(changeset)
end
def delete_book(%Book{}=book) do
@repo.delete(book)
end
end
以下是iex中的输出:
ex(3)> reviews = Foo.list_reviews
[debug] QUERY OK source="reviews" db=3.4ms
SELECT r0."id", r0."title", r0."contents", r0."stars", r0."inserted_at", r0."updated_at" FROM "reviews" AS r0 []
[]
## VALID DATA ###
iex(4)> Foo.insert_review(%{title: "book", contents: "good", stars: 4})
[debug] changeset.valid? => true
[debug] QUERY OK db=2.3ms queue=2.0ms
INSERT INTO "reviews" ("contents","stars","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" ["good", 4, "book", ~N[2019-07-10 17:23:06], ~N[2019-07-10 17:23:06]]
{:ok,
%Foo.Review{
__meta__: #Ecto.Schema.Metadata<:loaded, "reviews">,
contents: "good",
id: 4,
inserted_at: ~N[2019-07-10 17:23:06],
stars: 4,
title: "book",
updated_at: ~N[2019-07-10 17:23:06]
}}
## INVALID DATA ##
iex(5)> Foo.insert_review(%{title: "movie", contents: "shite", stars: 0})
[debug] changeset.valid? => true
[debug] QUERY ERROR db=6.1ms queue=1.5ms
INSERT INTO "reviews" ("contents","stars","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" ["shite", 0, "movie", ~N[2019-07-10 17:23:16], ~N[2019-07-10 17:23:16]]
{:error,
#Ecto.Changeset<
action: :insert,
changes: %{contents: "shite", stars: 0, title: "movie"},
errors: [
stars: {"stars must be between 1 and 5 (inclusive)",
[constraint: :check, constraint_name: "stars_range"]}
],
data: #Foo.Review<>,
valid?: false
>}
对于变更集中的错误,将changeset.valid?
设置为false
,然后返回{:error,changeset}
当验证器失败时,输出会有所不同。当检查约束失败时。如果我将验证更改为:
def changeset(%Foo.Review{}=review, attrs \\ %{}) do
review
|> cast(attrs, [:title, :contents, :stars])
|> validate_required(:title) ##<==== ADDED THIS VALIDATION
|> check_constraint(
:stars,
name: :stars_range,
message: "stars must be between 1 and 5 (inclusive)"
)
end
输出中的差异表明,只有在所有验证通过之后,Ecto才会尝试执行插入。当实际执行插入时,db会应用检查约束,这会导致插入失败,并且ecto会记录一个查询错误
底线是:仅仅因为变更集有效并不意味着插入会成功。如果changeset()
函数将约束添加到数据库中,则在调用@repo.insert(changeset)
实际执行插入之前,您无法知道插入变更集是否会成功 你试过这个吗?验证是否包含(:星号,1..5)?ref验证包含内容
——很好!
|> check_constraint(
:stars,
name: :stars_range,
message: "stars must be between 1 and 5 (inclusive)"
)
def changeset(%Foo.Review{}=review, attrs \\ %{}) do
review
|> cast(attrs, [:title, :contents, :stars])
|> validate_required(:title) ##<==== ADDED THIS VALIDATION
|> check_constraint(
:stars,
name: :stars_range,
message: "stars must be between 1 and 5 (inclusive)"
)
end
iex(6)> Foo.insert_review(%{contents: "crowded", stars: 1})
[debug] changeset.valid? => false
{:error,
#Ecto.Changeset<
action: :insert,
changes: %{contents: "crowded", stars: 1},
errors: [title: {"can't be blank", [validation: :required]}],
data: #Foo.Review<>,
valid?: false
>}
## INVALID DATA ##
iex(5)> Foo.insert_review(%{title: "movie", contents: "shite", stars: 0})
[debug] changeset.valid? => true
[debug] QUERY ERROR db=6.1ms queue=1.5ms
INSERT INTO "reviews" ("contents","stars","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" ["shite", 0, "movie", ~N[2019-07-10 17:23:16], ~N[2019-07-10 17:23:16]]
{:error,
#Ecto.Changeset<
action: :insert,
changes: %{contents: "shite", stars: 0, title: "movie"},
errors: [
stars: {"stars must be between 1 and 5 (inclusive)",
[constraint: :check, constraint_name: "stars_range"]}
],
data: #Foo.Review<>,
valid?: false
>}
[debug] QUERY ERROR db=6.1ms queue=1.5ms