Уникальное ограничение индекса, не работающее в приложении Phoenix
В моем приложении Phoenix у меня есть модель User следующим образом:
defmodule MyApp.User do
use MyApp.Web, :model
schema "users" do
field :username, :string, unique: true
field :email, :string, unique: true
field :crypted_password, :string
field :password, :string, virtual: true
timestamps
end
@required_fields ~w(email password username)
@optional_fields ~w()
@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(model, params \\ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
|> unique_constraint(:email)
|> unique_constraint(:username)
|> validate_format(:email, ~r/@/)
|> validate_length(:password, min: 5)
end
end
У меня также есть следующая миграция:
defmodule MyApp.Repo.Migrations.CreateUser do
use Ecto.Migration
def change do
create table(:users) do
add :email, :string
add :username, :string
add :crypted_password, :string
timestamps
end
create unique_index(:users, [:email])
create unique_index(:users, [:username])
end
end
И у меня есть registration_controller_ex
следующим образом:
defmodule MyApp.RegistrationController do
use MyApp.Web, :controller
alias MyApp.User
def new(conn, _params) do
changeset = User.changeset(%User{})
render conn, changeset: changeset
end
def create(conn, %{"user" => user_params}) do
changeset = User.changeset(%User{}, user_params)
if changeset.valid? do
user = MyApp.Registration.create(changeset, MyApp.Repo)
conn
|> put_flash(:info, "Your account was created")
|> redirect(to: "/")
else
conn
|> put_flash(:info, "Unable to create account")
|> render("new.html", changeset: changeset)
end
end
end
Итак, при всем этом я уверен, что мои поля username
и email
в User - это уникальные индексы. Я также удостоверяюсь, что они уникальны, вызвав unique_constraint
для проверки User.changeset. Однако, когда в моем интерфейсе я создаю пользователя с тем же адресом электронной почты и именем пользователя как предварительно созданный, набор изменений проверяется и пользователь "создается". (он фактически не создается, когда я смотрю в базу данных, ничего не добавляется)
Я получаю следующее на своих журналах сервера, но мой changeet.valid? истинно.
[debug] BEGIN [] OK query=139.3ms queue=8.2ms
[debug] INSERT INTO "users" ("crypted_password", "email", "inserted_at", "updated_at", "username") VALUES ($1, $2, $3, $4, $5) RETURNING "id" ["$2b$12$MN1YxFUGLMIJYXseZn0sjuuVs9U1jRdYtRr9D8XQsAqdh.D2sRRXa", "[email protected]", {{2015, 9, 30}, {11, 7, 25, 0}}, {{2015, 9, 30}, {11, 7, 25, 0}}, "username"] ERROR query=5.5ms
[debug] ROLLBACK [] OK query=0.4ms
Кроме того, другие пользователи, которые ищут в моей функции User.changeset(например, минимальная длина пароля и другие вещи), сообщаются пользователю и работают нормально. Это просто уникальные индексы для :email
и :username
, которые не сообщаются.
Ответы
Ответ 1
unique_constraint
будет проверяться базой данных и, таким образом, запускаться только после вставки записи. Вызов changeset.valid?
не проверяет ограничения и, следовательно, в этом случае возвращает true. Вам нужно проверить верный кортеж в папке Repo и действовать так:
def create(conn, %{"user" => user_params}) do
changeset = User.changeset(%User{}, user_params)
case MyApp.Repo.insert changeset do
{:ok, changeset} ->
conn
|> put_flash(:info, "Your account was created")
|> redirect(to: "/")
{:error, changeset} ->
conn
|> put_flash(:info, "Unable to create account")
|> render("new.html", changeset: changeset)
end
end
теперь ваш changeset.errors
обогащен, и вы можете получить ошибку с помощью changeset.errors[:email]