В мой ответ на ваш предыдущий вопрос, , если я добавлю какой-нибудь вывод при создании набора изменений для вставки:
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 возвращает неверный набор изменений.
Это потому, что проверочное ограничение является правилом БД, а не валидатором. Функция 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)
.