2017-08-21 49 views
0

道歉,如果這聽起來像一個愚蠢的問題,但我被難以置信的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" => "[email protected]", 
     "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) 
+0

你可以發佈錯誤的整個堆棧跟蹤? – Dogbert

+0

@Dogbert我已經編輯帖子以包括堆棧跟蹤 –

回答

1

TL;博士

通話 「中投」 功能轉換將結構體模型化爲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

根據代碼粘貼:

@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。使用的castcast_assoc之前,將會使attr到是一個changeset,你可以忽略那user's changeset/2檢查userattrs那將在水下完成

並順便說一下,只能創建新的userguard,您可以使用cast_assoc

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