外对一多态性关联

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

operatoroperator_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在水下完成

顺便说一句,只创建新的userguard,可以使用cast_assoc

如果userguard 已经存在,我建议使用 put_assocbuild_assoc

【讨论】:

以上是关于外对一多态性关联的主要内容,如果未能解决你的问题,请参考以下文章

2021-07-05

Laravel-多对一多态关系

详细说明:方法重载是静态/编译时绑定,但不是多态性。将静态绑定与多态性相关联是不是正确?

如何在 Mirage js 中播种具有多态一对一关系的模型?

具有最终类的多态性,可在 swift 中实现关联类型协议

rails中的多态关联