Ecto一对一的多态关联

时间:2017-08-21 15:13:55

标签: elixir phoenix-framework ecto

道歉,如果这听起来像一个愚蠢的问题,但我被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.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)

2 个答案:

答案 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

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

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

相关问题