复合键,`unique_constraint`问题

时间:2016-08-13 20:24:37

标签: elixir ecto

我的迁移方式如下:

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)

1 个答案:

答案 0 :(得分:0)

所以问题是你将索引名称直接作为第3个参数传递给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)