我的迁移方式如下:
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)
答案 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)