#elixir #phoenix-framework #ecto
#elixir #phoenix-framework #ecto
Вопрос:
То, что я ожидал, произойдет: при попытке вставить persona с уже существующим именем пользователя для вызова Repo.insert, чтобы вернуть мне набор изменений с ошибкой в деталях.
Что происходит: 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
Комментарии:
1. Вы использовали миграцию для создания индекса? Если да, не могли бы вы также добавить код переноса?
2. Да, я внес изменения в сообщение, добавив дополнительный файл миграции.
Ответ №1:
Я думаю, что вы должны вставить набор изменений, а не схему напрямую, чтобы иметь возможность перехватить ошибку, как описано в документах:
https://hexdocs.pm/ecto/Ecto.Набор изменений.html#unique_constraint/3-complex-constraints
Вы вызываете unique_constraint/3
в своей Persona.changeset/2
функции, но вы не вызываете Persona.changeset/2
в коде вашего контроллера.
Я считаю, что вам следует заменить следующую строку:
result = Repo.insert(%Persona{username: username, user: user}
Автор::
result = %Persona{}
|> Persona.changeset(%{username: username, user: user})
|> Repo.insert()
Или, скорее, вероятно, более идиоматично иметь create_persona/1
функцию, определенную в контексте, которая это делает, и вызывать эту функцию из вашего контроллера (по крайней мере, так это организует генератор).
Комментарии:
1. Спасибо, это изменение решило проблему! Да, я полагаю, проблема была в том, что я не передавал ему набор изменений. Вы упомянули контекст; я сгенерировал свою схему без контекста. Есть ли способ добавить контекст к уже сгенерированным схемам? Прошу прощения, я новичок в Elixir / Phoenix. У меня есть
User
,Persona
иAuthToken
схемы, которые я хотел бы сгруппировать вAccounts
контекст.2. Я думаю, вы можете попробовать запустить
mix phx.gen.context
еще раз. hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Context.html#content — возможно, прямо в вашем проекте и посмотрите, как это работает — если результат слишком беспорядочный, вы можете создать новое пустое приложение, запустить эти команды там, затем скопироватьAccounts
файл контекста в свой проект. Это не должно требовать слишком много изменений, это почти всегда одно и то же.