Elixir Ecto 2:自引用many_to_many和Ecto.Changeset.put_assoc。怎么样?

时间:2016-11-24 14:48:39

标签: elixir ecto

我正在尝试在Ecto 2中创建一个自引用many_to_many关系。我关注了this blogpost并让它工作到目前为止。但是,尝试更新与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 

这现在有效:

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

我在这里做错了什么?

1 个答案:

答案 0 :(得分: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选项,因为我认为没有它,Ecto可能会在这种情况下感到困惑。我建议你尝试使用和不使用它。

使用many_to_many,您可以将MyApp.User更改集直接插入:contacts关联,这似乎就是您想要做的。