我的模型中有这个check_constraint。
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")
但是当我运行此测试时,变更集有效吗?我希望它是无效的,因为我要将整数7传递给stars
列。其约束为1 through 5
。有人知道这是怎么回事吗?
test "requires stars to be within range of 1-5" do
user = insert(:user)
project = insert(:project, owner: user)
user_project_map = %{project_id: project.id, user_id: user.id}
review_map = Map.merge(@valid_attrs, user_project_map)
attrs = %{review_map | stars: 7}
changeset = Review.changeset(%Review{}, attrs)
refute changeset.valid?
end
答案 0 :(得分:1)
引用docs:
(...)现在,当调用Repo.insert / 2或Repo.update / 2时,如果价格不是正数,它将转换为错误,并由存储库返回{:error,changeset}。请注意,该错误仅在访问数据库后才会发生,因此只有通过所有其他验证之后,该错误才可见。
这意味着check_constraint
仅在查询命中数据库时发生。因此,在实际调用数据库之前检查验证时,您的changeset.valid?
返回true
。您创建的约束是在数据库内部创建的,因此Ecto实际上无法在调用之前知道该约束实际检查的内容。通常,此类约束用于更复杂的检查,或者您是否已经在数据库中定义了约束(也许是因为您是从另一个系统迁移数据库的?)。如果您想查看自己的行动约束,则应该在测试中编写:
attrs = %{review_map | stars: 7}
changeset = Review.changeset(attrs)
{:error, changeset} = Repo.insert(changeset)
refute changeset.valid?
如果在调用数据库之前需要Changeset
检查某些条件,则应使用validate_inclusion/4
或validate_subset/4
之类的函数。您甚至可以使用validate_change/4
编写自己的检查器(如果需要更多说明,请告诉我)。如果使用这些验证器,则更改集将在调用数据库之前起作用。
答案 1 :(得分:0)
在my answer to your previous question,中,如果在为插入创建变更集时添加一些输出:
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
>}
对于无效数据,您可以看到更改集在调用@repo.insert(changeset)
之前是有效的,然后在插入失败之后Ecto返回了无效的更改集。
那是因为检查约束是db规则-不是验证程序。 changeset()函数将应用您指定的所有验证器,从而确定更改集是否有效。如果更改集有效,则Ecto实际上尝试在数据库中进行插入。此时,数据库将执行检查约束,以确定插入是否成功。如果检查约束失败,则数据库将引发错误。 ecto捕获到该错误,然后添加您在此处指定的消息:
|> check_constraint(
:stars,
name: :stars_range,
message: "stars must be between 1 and 5 (inclusive)"
)
针对变更集中的错误,将changeset.valid?
设置为false
,然后返回{:error, changeset}
。
当验证器失败时v。当检查约束失败时,输出会有所不同。如果我将验证更改为:
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
输出的差异表明,只有在所有验证通过之后,Ecto才会尝试执行插入。当实际执行插入操作时,数据库将应用检查约束,这将导致插入操作失败,并且ecto记录QUERY ERROR
。
最重要的是:仅仅因为变更集是有效的,并不意味着插入将成功。如果changeset()
函数向数据库中添加了constraints
,那么直到您通过调用@repo.insert(changeset)
实际执行插入操作,您才能知道插入变更集是否会成功。