外一对一多态关联

Ecto one to one polymorphic assocation

如果这听起来像是一个愚蠢的问题,我深表歉意,但我对 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 table 也是如此。

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)

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.

根据您粘贴的代码:

 @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 .