复合键,`unique_constraint` 问题

Composite key, `unique_constraint` issue

我有这样的迁移:

defmodule N.Repo.Migrations.CreateAuth do
  use Ecto.Migration

  def change do
    create table(:auths, primary_key: false) do
      add :oauth_id, :string, primary_key: true
      add :provider, :string, primary_key: true

      add :user_id, references(:users, on_delete: :nothing, type: :binary_id)

      timestamps()
    end

    create index(:auths, [:user_id])
  end
end

并且型号配置如下:

defmodule N.Auth do
  use N.Web, :model

  @primary_key false
  schema "auths" do
    field :oauth_id, :string, primary_key: true
    field :provider, :string, primary_key: true

    belongs_to :user, N.User

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \ %{}) do
    struct
    |> cast(params, [:oauth_id, :provider])
    |> validate_required([:oauth_id, :provider])
    |> unique_constraint(:oauth_id, :auths_oauth_id_index)
  end
end

遵循 Phoenix guidelines 的建议。

在我的测试中:

defmodule N.AuthTest do
  use N.ModelCase

  import N.Factory

  alias N.Auth

  test "changeset with duplicates" do
    auth_params =
      %{provider: :facebook,
        oauth_id: "1234"}
    insert(:auth, auth_params)

    changeset = Auth.changeset(%Auth{}, auth_params)

    assert {:error, changeset} = Repo.insert(changeset)
  end
end

不幸的是,当 运行宁他们时,我有以下错误:

  1) test N.Auth.changeset/2 changeset with duplicates (N.AuthTest)
     test/models/auth_test.exs:31
     ** (FunctionClauseError) no function clause matching in Access.fetch/2
     stacktrace:
       (elixir) lib/access.ex:147: Access.fetch(:auths_oauth_id_index, :name)
       (elixir) lib/access.ex:179: Access.get/3
       (ecto) lib/ecto/changeset.ex:1629: Ecto.Changeset.unique_constraint/3
       test/models/auth_test.exs:37: (test)

如果我没理解错的话,这是因为缺少:

create unique_index(:auths, [:oauth_id, :provider])

unique_constraint/3 文档中所述。显然,我不想创建索引,因为它是通过设置复合主键自动创建的。

你有什么进一步的建议吗?

更新

此处使用的索引名称由 Ecto 本身建议 - 当我使用代码时未指定索引名称,如下所示:

    |> unique_constraint(:oauth_id)

我在运行测试的时候,报错信息如下:

  1) test N.Auth.changeset/2 changeset with duplicates (N.AuthTest)
     test/models/auth_test.exs:31
     ** (Ecto.ConstraintError) constraint error when attempting to insert struct:

         * unique: auths_pkey

     If you would like to convert this constraint into an error, please
     call unique_constraint/3 in your changeset and define the proper
     constraint name. The changeset defined the following constraints:

         * unique: auths_oauth_id_index

     stacktrace:
       (ecto) lib/ecto/repo/schema.ex:403: anonymous fn/4 in Ecto.Repo.Schema.constraints_to_errors/3
       (elixir) lib/enum.ex:1183: Enum."-map/2-lists^map/1-0-"/2
       (ecto) lib/ecto/repo/schema.ex:393: Ecto.Repo.Schema.constraints_to_errors/3
       (ecto) lib/ecto/repo/schema.ex:193: anonymous fn/11 in Ecto.Repo.Schema.do_insert/4
       (ecto) lib/ecto/repo/schema.ex:595: anonymous fn/3 in Ecto.Repo.Schema.wrap_in_transaction/6
       (ecto) lib/ecto/adapters/sql.ex:472: anonymous fn/3 in Ecto.Adapters.SQL.do_transaction/3
       (db_connection) lib/db_connection.ex:973: DBConnection.transaction_run/4
       (db_connection) lib/db_connection.ex:897: DBConnection.run_begin/3
       (db_connection) lib/db_connection.ex:671: DBConnection.transaction/3
       test/models/auth_test.exs:41: (test)

当我尝试 check_constraint/3 时出现同样的错误,例如:

    |> check_constraint(:oauth_id, name: :auths_oauth_id_index)

所以问题是您将索引名称作为第三个参数直接传递给 unique_constraint,而它需要是具有键 :name.

的关键字列表项

我的第一个线索是这条错误信息:

Access.fetch(:auths_oauth_id_index, :name)

并且您传递的第三个参数是 :auths_oauth_id_index。该错误意味着 Ecto 试图从值访问 :name 键,并且由于它不是列表或映射,因此它抛出错误。

此外,索引名称不是 :auths_oauth_id_index,而是 Ecto 分配给主键的默认名称::auths_pkey(格式为 table_name <> "_pkey")。

最终工作代码:

|> unique_constraint(:‌​oauth_id, name: :auths_pkey)