2016-11-24 62 views
2

我試圖在Ecto 2中創建一個自引用many_to_many的關係。我跟着this blogpost到目前爲止工作。但試圖更新與Ecto.Changeset.put_assoc的關聯總是會導致錯誤。我不明白爲什麼。Elixir Ecto 2:自引用many_to_many和Ecto.Changeset.put_assoc。怎麼樣?

這是設置:

首先遷移到創建用戶和聯繫人的關聯表(每個用戶可以擁有多個聯繫人這也是用戶):

# priv/repo/migrations/create_users_table.ex 
defmodule MyApp.Repo.Migrations.CreateUsersTable do 
    use Ecto.Migration 

    def change do 
    create table(:users) do 
     add :username, :string 
    end 

    create unique_index(:users, [:username]) 
    end 
end 

# priv/repo/migrations/create_contacts_table.ex 
defmodule MyApp.Repo.Migrations.CreateContactsTable do 
    use Ecto.Migration 

    def change do 
    create table(:contacts) do 
     add :user_id, references(:users, on_delete: :nothing), primary_key: true 
     add :contact_id, references(:users, on_delete: :nothing), primary_key: true 

     timestamps() 
    end 
    end 
end 

現在型號:

defmodule MyApp.User do 
    use MyApp.Web, :model 
    alias MyApp.Contact 

    schema "users" do 
    field :username, :string 
    # Add the many-to-many association 
    has_many :_contacts, MyApp.Contact 
    has_many :contacts, through: [:_contacts, :contact] 
    timestamps 
    end 

    # Omitting changesets 
end 

defmodule MyApp.Contact do 
    use MyApp.Web, :model 

    alias MyApp.User 

    schema "contacts" do 
    belongs_to :user, User 
    belongs_to :contact, User 
    end 
end 

This now works:

user = Repo.get!(User, 1) |> Repo.preload :contacts 
user.contacts 

現在我試圖解析逗號分隔的ID的字符串,獲取用戶,把他們變成變更後再進行安裝聯繫人到另一個用戶

# Parse string and get list of ids 
contact_ids = String.split("2, 3, 4", ",") |> Enum.map(&String.trim/1) 

# Get contacts 
contacts = Enum.map(contact_ids, fn(id) -> 
    Repo.get! User, id 
end) 

# Turn them into changesets 
contact_changesets = Enum.map(contacts, &Ecto.Changeset.change/1) 

# Update the associations 
result = user |> Ecto.Changeset.change 
|> Ecto.Changeset.put_assoc(:contacts, contact_changesets) 
|> Repo.update 

我的錯誤得到是

** (ArgumentError) cannot put assoc `contacts`, assoc `contacts` not found. Make sure it is spelled correctly and properly pluralized (or singularized) 
    (ecto) lib/ecto/changeset.ex:568: Ecto.Changeset.relation!/4 
    (ecto) lib/ecto/changeset.ex:888: Ecto.Changeset.put_relation/5 

但我可以預加載關聯,我也可以手動創建關聯。所以我可以循環contact_ids並執行此操作:

result = user 
|> Ecto.Changeset.change 
|> Ecto.Changeset.put_assoc(:contacts, [Contact.changeset(%Contact{}, %{user_id: user_id, contact_id: contact_id})]) 
|> Repo.insert 

我在做什麼錯在這裏?

+0

是否有任何理由不僅僅使用['many_to_many'](https://hexdocs.pm/ecto/Ecto.Schema.html#many_to_many/3)? –

+0

@JustinWood我只能引用[博客文章](http:// swanros。com/self-referencing-many-to-many-relationships-using-ecto /):'Elixir社區的一位成員告訴我Slack,這看起來更像是一對多的關係,我應該嘗試在我的應用中代表這種方式。 [...]在我的Phoenix應用程序的實際測試過程中,我注意到Ecto使用前一種關聯類型生成的查詢是錯誤的。如上所述將它從many_to_many切換到has_many,解決了問題 –

回答

2

我無法重現與我自己的關聯問題。我有一種感覺,你可能在某種程度上與%MyApp.Contact{}而不是%MyApp.User{}?你可以檢查並報告回來嗎?

我注意到了一些東西(但不會產生此錯誤):您正嘗試將MyApp.User變更集放入:contacts關聯中,這需要MyApp.Contact變更集。您可以嘗試使用many_to_many。你可以確定你拿回MyApp.User,所以會有這樣的邊緣情況。無論如何,它是專門爲這些類型的協會製作的。

MyApp.User模式:

schema "users" do 
    field :username, :string 

    many_to_many :contacts, MyApp.User, join_through: MyApp.Contact, join_keys: [user_id: :id, contact_id: id] 
    timestamps 
end 

我加了join_keys選項,因爲我覺得沒有它外生可能在這種情況下會感到困惑。我建議你嘗試和沒有它。

使用many_to_many您可以將MyApp.User變更集直接插入到:contacts關聯中,這似乎是您想要執行的操作。