道歉,如果这听起来像一个愚蠢的问题,但我被Ecto抛出的错误所困扰。
我正在尝试实现一对一的多态关联。我已经阅读了有关在Ecto中实现多态关联的方法的文档,但我的要求需要一对一的关系。
guard -> guard_user -> user
operator -> operator_user -> user
,其中
guard
-----
id
position
guard_user
----
guard_id
user_id
user
-----
id
email
password
name
同样适用于operator
和operator_user
表格。
defmodule Example.Guards.Guard do
use Ecto.Schema
import Ecto.Changeset
alias Example.Guards.Guard
alias Example.Guards.GuardUser
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "guards" do
field :position, :string
has_one :guard_user, GuardUser
has_one :user, through: [:guard_user, :user]
timestamps()
end
@doc false
def changeset(%Guard{} = guard, attrs \\ %{}) do
guard
|> cast(attrs, [:position])
|> cast_assoc(:guard_user, required: true)
end
end
defmodule Example.Guards.GuardUser do
use Ecto.Schema
import Ecto.Changeset
alias Example.Guards.Guard
alias Example.Accounts.User
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "guard_user" do
belongs_to :guard, Guard
belongs_to :user, User
timestamps()
end
@doc false
def changeset(guard_user, attrs \\ %{}) do
guard_user
|> cast_assoc(:user, required: true)
end
end
defmodule Example.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
alias Example.Accounts.User
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "users" do
field :email, :string
field :password, :string
timestamps()
end
@doc false
def changeset(%User{} = user, attrs) do
user
|> cast(attrs, [:email, :password])
|> validate_required([:email, :password])
|> unique_constraint(:email)
end
end
当我进行测试时,
test "create guard" do
params = %{
"position" => "Guard",
"guard_user" => %{
"user" => %{
"email" => "example@gmail.com",
"password" => "example"
}
}
}
changeset = Guard.changeset(%Guard{}, params)
assert changeset.valid?
end
抛出以下错误:
** (FunctionClauseError) no function clause matching in Ecto.Changeset.cast_relation/4
The following arguments were given to Ecto.Changeset.cast_relation/4:
# 1
:assoc
# 2
%Example.Guards.GuardUser{__meta__: #Ecto.Schema.Metadata<:built, "guard_user">, guard: #Ecto.Association.NotLoaded<association :guard is not loaded>, guard_id: nil, id: nil, inserted_at: nil, updated_at: nil, user: #Ecto.Association.NotLoaded<association :user is not loaded>, user_id: nil}
# 3
:user
# 4
[required: true]
Attempted function clauses (showing 2 out of 2):
defp cast_relation(type, %Ecto.Changeset{data: data, types: types}, _name, _opts) when data == nil or types == nil
defp cast_relation(type, %Ecto.Changeset{} = changeset, key, opts)
code: changeset = Guard.changeset(%Guard{}, params)
stacktrace:
(ecto) lib/ecto/changeset.ex:665: Ecto.Changeset.cast_relation/4
(ecto) lib/ecto/changeset.ex:712: anonymous fn/4 in Ecto.Changeset.on_cast_default/2
(ecto) lib/ecto/changeset/relation.ex:100: Ecto.Changeset.Relation.do_cast/5
(ecto) lib/ecto/changeset/relation.ex:237: Ecto.Changeset.Relation.single_change/5
(ecto) lib/ecto/changeset.ex:691: Ecto.Changeset.cast_relation/4
test/example/guards/guard_test.exs:29: (test)
答案 0 :(得分:1)
<强> TL;博士
打电话&#34;演员&#34;函数将模型结构转换为Ecto.Changeset结构。
defmodule Example.Guards.GuardUser do
use Ecto.Schema
import Ecto.Changeset
alias Example.Guards.Guard
alias Example.Guards.GuardUser
alias Example.Accounts.User
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "guard_user" do
belongs_to :guard, Guard
belongs_to :user, User
timestamps()
end
@doc false
def changeset(%GuardUser{} = guard_user, attrs \\ %{}) do
guard_user
|> cast(attrs, []) # <----------------- here -----------
|> cast_assoc(:user, required: true)
end
end
错误消息试图告诉您有两个 cast_relation 函数,这些函数需要 %Ecto.Changeset 但您传入了 %Example.Guards.GuardUser 。
答案 1 :(得分:1)
根据您粘贴的代码:
@doc false
def changeset(guard_user, attrs \\ %{}) do
guard_user
|> cast_assoc(:user, required: true)
end
事实上,你应该先Ecto.Changeset.cast(attrs, [])
,像这样:
@doc false
def changeset(guard_user, attrs \\ %{}) do
guard_user
|> cast(attrs, [])
|> cast_assoc(:user, required: true)
end
查看Ecto document。在cast
之前使用cast_assoc
,将attr
设为changeset
,您可以忽略user's changeset/2
1}}检查将在水下完成的user
attrs
顺便说一句,只创建新的user
或guard
,您可以使用cast_assoc
。
如果user
或guard
已存在,我建议使用
put_assoc
或build_assoc
。