外对一多态性关联
Posted
技术标签:
【中文标题】外对一多态性关联【英文标题】:Ecto one to one polymorphic assocation 【发布时间】:2017-08-21 15:13:54 【问题描述】:抱歉,如果这听起来像是一个愚蠢的问题,但我被 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.Changesetdata: 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)
【问题讨论】:
你能发布错误的整个堆栈跟踪吗? @Dogbert 我已经编辑了帖子以包含堆栈跟踪 【参考方案1】:tl;dr
调用“cast”函数将模型结构转换为 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。
【讨论】:
【参考方案2】:根据你粘贴的代码:
@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_assoc
之前使用cast
,将使attr
成为changeset
,您可以省略user's changeset/2
检查user
attrs
在水下完成
顺便说一句,只创建新的user
或guard
,可以使用cast_assoc
。
如果user
或guard
已经存在,我建议使用
put_assoc
或 build_assoc
。
【讨论】:
以上是关于外对一多态性关联的主要内容,如果未能解决你的问题,请参考以下文章
详细说明:方法重载是静态/编译时绑定,但不是多态性。将静态绑定与多态性相关联是不是正确?