【问题标题】: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.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)

【问题讨论】:

  • 你能发布错误的整个堆栈跟踪吗?
  • @Dogbert 我已经编辑了帖子以包含堆栈跟踪

标签: elixir phoenix-framework ecto


【解决方案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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多