将模型插入到与ecto 2中的现有模型的多对多关系中

时间:2016-05-24 12:32:47

标签: phoenix-framework ecto

我正在尝试使用Ecto 2 rc。

我的模特是:

schema "containers" do
  field :name, :string
  many_to_many :items,  Test.Item, join_through: Test.ContainerItem, on_delete: :delete_all

  timestamps
end

schema "items" do
  field :content, :string
  many_to_many :containers, Test.Container, join_through: Test.ContainerItem, on_delete: :delete_all

  timestamps
end

schema "containers_items" do
  belongs_to :container, Test.Container
  belongs_to :item, Test.Item

  timestamps
end

我的控制器代码是:

def add_item(conn, %{"item" => item_params, "container_id" => container_id}) do
  item = Item.changeset(%Item{}, item_params)
  IO.inspect(item) #TODO remove
  container = Container |> Repo.get(container_id) |> Repo.preload([:items])
  IO.inspect(container) #TODO remove

  changeset = container
    |> Ecto.Changeset.change
    |> Ecto.Changeset.put_assoc(:items, [item])

  IO.inspect(changeset) #TODO remove

  if changeset.valid? do
    Repo.update(changeset)

    conn
    |> put_flash(:info, "Item added.")
    |> redirect(to: container_path(conn, :show, container))
  else
    render(conn, "show.html", container: container, changeset: changeset)
  end
end

现在,如果我将一个项目添加到容器中,这可以正常工作。但是,如果容器上存在某个项目,那么尝试添加另一个项目会给我:

  

(RuntimeError)你试图改变关系:的项目   Test.Container,但缺少数据。

我情不自禁地觉得我的方式错了,一些建议值得赞赏。

1 个答案:

答案 0 :(得分:3)

好的,所以我只想出来了。

我的问题是没有将项目转换为Changesets,以便ecto可以跟踪需要进行的更改。

我需要进行的唯一编辑是控制器。

它应该是这样的:

def add_item(conn, %{"item" => item_params, "container_id" => container_id}) do
  item = Item.changeset(%Item{}, item_params)
  IO.inspect(item) #TODO remove
  container = Container |> Repo.get(container_id) |> Repo.preload([:items])
  IO.inspect(container) #TODO remove

  item_changesets = Enum.map([item | container.items], &Ecto.Changeset.change/1)

  changeset = container
    |> Ecto.Changeset.change
    |> Ecto.Changeset.put_assoc(:items, item_changesets)

  IO.inspect(changeset) #TODO remove

  if changeset.valid? do
    Repo.update(changeset)

    conn
    |> put_flash(:info, "Item added.")
    |> redirect(to: container_path(conn, :show, container))
  else
    render(conn, "show.html", container: container, changeset: changeset)
  end
end