Phoenix/Elixir/Ecto - unique_constraint 无法使用名称选项
Phoenix/Elixir/Ecto - unique_constraint not working with name option
我预期会发生什么: 当试图插入一个已经存在用户名的角色时,为了调用 Repo.insert 到 return 我详细信息错误的变更集。
发生了什么: Ecto 引发硬异常
我的 Phoenix 项目出现以下错误。
** (exit) an exception was raised:
** (Ecto.ConstraintError) constraint error when attempting to insert struct:
* personas_username_index (unique_constraint)
If you would like to stop this constraint violation from raising an
exception and instead add it as an error to your changeset, please
call `unique_constraint/3` on your changeset with the constraint
`:name` as an option.
The changeset has not defined any constraint.
所以我听从了它的建议,在我的数据库中添加了唯一索引的名称,personas_username_index
。这个唯一索引肯定存在于真正的底层数据库中。
这是我的架构:
defmodule Poaster.Persona do
use Ecto.Schema
import Ecto.Changeset
alias Poaster.User
schema "personas" do
belongs_to :user, User
field :background_image_url, :string
field :bio, :string
field :name, :string
field :profile_image_url, :string
field :username, :string
timestamps()
end
@doc false
def changeset(persona, attrs) do
persona
|> cast(attrs, [:username, :name, :bio, :profile_image_url, :background_image_url, :user])
|> validate_required([:username])
|> unique_constraint(:username, name: :personas_username_index)
end
end
这是我的控制器代码:
def create(conn, %{"username" => username}) do
# First attempt
# result = Ecto.build_assoc(conn.assigns[:signed_user], :personas, %{ username: username })
# IO.inspect(result)
# result = Repo.insert(result)
# IO.inspect(result)
user = conn.assigns[:signed_user]
result = Repo.insert(%Persona{username: username, user: user})
case result do
{:ok, persona} ->
conn
|> put_status(:created)
|> json(persona_data(persona))
{:error, _changeset} ->
conn
|> put_status(:internal_server_error)
|> json(%{
success: false,
error: %{
detail: "There was an error saving your username!"
}
})
end
end
defp persona_data(persona) do
%{
id: persona.id,
background_image_url: persona.background_image_url,
bio: persona.bio,
inserted_at: persona.inserted_at,
name: persona.name,
profile_image_url: persona.profile_image_url,
updated_at: persona.updated_at,
username: persona.username
}
end
迁移文件:
defmodule Poaster.Repo.Migrations.CreatePersonas do
use Ecto.Migration
def change do
create table(:personas) do
add :username, :string, null: false
add :name, :string
add :bio, :string
add :profile_image_url, :string
add :background_image_url, :string
add :user_id, references(:users, on_delete: :nothing), null: false
timestamps()
end
create unique_index(:personas, [:username])
create index(:personas, [:user_id])
end
end
我认为您应该插入一个变更集,而不是直接插入模式,以便能够捕获错误,如文档中所述:
https://hexdocs.pm/ecto/Ecto.Changeset.html#unique_constraint/3-complex-constraints
您正在 Persona.changeset/2
函数中调用 unique_constraint/3
,但您没有在控制器代码中调用 Persona.changeset/2
。
我认为您应该替换以下行:
result = Repo.insert(%Persona{username: username, user: user}
作者:
result = %Persona{}
|> Persona.changeset(%{username: username, user: user})
|> Repo.insert()
或者更确切地说,在执行此操作的上下文中定义一个 create_persona/1
函数并从您的控制器调用该函数(至少生成器是这样组织它的)可能更符合习惯。
我预期会发生什么: 当试图插入一个已经存在用户名的角色时,为了调用 Repo.insert 到 return 我详细信息错误的变更集。
发生了什么: Ecto 引发硬异常
我的 Phoenix 项目出现以下错误。
** (exit) an exception was raised:
** (Ecto.ConstraintError) constraint error when attempting to insert struct:
* personas_username_index (unique_constraint)
If you would like to stop this constraint violation from raising an
exception and instead add it as an error to your changeset, please
call `unique_constraint/3` on your changeset with the constraint
`:name` as an option.
The changeset has not defined any constraint.
所以我听从了它的建议,在我的数据库中添加了唯一索引的名称,personas_username_index
。这个唯一索引肯定存在于真正的底层数据库中。
这是我的架构:
defmodule Poaster.Persona do
use Ecto.Schema
import Ecto.Changeset
alias Poaster.User
schema "personas" do
belongs_to :user, User
field :background_image_url, :string
field :bio, :string
field :name, :string
field :profile_image_url, :string
field :username, :string
timestamps()
end
@doc false
def changeset(persona, attrs) do
persona
|> cast(attrs, [:username, :name, :bio, :profile_image_url, :background_image_url, :user])
|> validate_required([:username])
|> unique_constraint(:username, name: :personas_username_index)
end
end
这是我的控制器代码:
def create(conn, %{"username" => username}) do
# First attempt
# result = Ecto.build_assoc(conn.assigns[:signed_user], :personas, %{ username: username })
# IO.inspect(result)
# result = Repo.insert(result)
# IO.inspect(result)
user = conn.assigns[:signed_user]
result = Repo.insert(%Persona{username: username, user: user})
case result do
{:ok, persona} ->
conn
|> put_status(:created)
|> json(persona_data(persona))
{:error, _changeset} ->
conn
|> put_status(:internal_server_error)
|> json(%{
success: false,
error: %{
detail: "There was an error saving your username!"
}
})
end
end
defp persona_data(persona) do
%{
id: persona.id,
background_image_url: persona.background_image_url,
bio: persona.bio,
inserted_at: persona.inserted_at,
name: persona.name,
profile_image_url: persona.profile_image_url,
updated_at: persona.updated_at,
username: persona.username
}
end
迁移文件:
defmodule Poaster.Repo.Migrations.CreatePersonas do
use Ecto.Migration
def change do
create table(:personas) do
add :username, :string, null: false
add :name, :string
add :bio, :string
add :profile_image_url, :string
add :background_image_url, :string
add :user_id, references(:users, on_delete: :nothing), null: false
timestamps()
end
create unique_index(:personas, [:username])
create index(:personas, [:user_id])
end
end
我认为您应该插入一个变更集,而不是直接插入模式,以便能够捕获错误,如文档中所述:
https://hexdocs.pm/ecto/Ecto.Changeset.html#unique_constraint/3-complex-constraints
您正在 Persona.changeset/2
函数中调用 unique_constraint/3
,但您没有在控制器代码中调用 Persona.changeset/2
。
我认为您应该替换以下行:
result = Repo.insert(%Persona{username: username, user: user}
作者:
result = %Persona{}
|> Persona.changeset(%{username: username, user: user})
|> Repo.insert()
或者更确切地说,在执行此操作的上下文中定义一个 create_persona/1
函数并从您的控制器调用该函数(至少生成器是这样组织它的)可能更符合习惯。