Phoenix / Elixir / Ecto-unique_constraint无法使用名称选项

问题描述

我希望发生的事情:当尝试插入具有现有用户名的角色时,请致电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

解决方法

我认为您应该插入一个变更集,而不是直接插入架构,以便能够捕获错误,如文档所述:

https://hexdocs.pm/ecto/Ecto.Changeset.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函数,然后从您的控制器调用此函数(至少这是生成器对其进行组织的方式),可能更习惯。