Updating many to many relation

I’m trying to learn how to create a many to many relation as explained in What's new in Ecto 2.0. I’m able to create new relations in my app between people and books with this code, but when I try to update a relation I’m getting this error:

you are attempting to change relation :people of MyDb.Book, but there is missing data.

/web/models/book.ex

defmodule MyDb.Book do
use MyDb.Web, :model
  schema "books" do
    field :title, :string
    field :note, :string
    many_to_many :people, MyDb.Person, join_through: MyDb.BookPerson
    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:title, :note])
    |> put_assoc(:people, parse_people_ids(params))
  end

  defp parse_people_ids(params) do
    (params["people_ids"] || [])
    |> Enum.map(&get_people/1)
  end

  defp get_people(id) do
      MyDb.Repo.get_by(MyDb.Person, id: id)
  end
end

I am able to save relation with this changeset.

/web/controllers/book_controller.ex

  def edit(conn, %{"id" => id}) do
    book = Repo.get!(Book, id)|> Repo.preload(:people) 
    people = Repo.all(Person) |> Enum.map(&{&1.surname, &1.id})
    changeset = Book.changeset(book)
    render(conn, "edit.html", book: book, changeset: changeset, conn: conn, people: people)
  end

  def update(conn, %{"id" => id, "book" => book_params}) do
    book = Repo.get!(Book, id)
    changeset = Book.changeset(book, book_params)

    case Repo.update(changeset) do
      {:ok, book} ->
        conn
        |> put_flash(:info, "Book updated successfully.")
        |> redirect(to: book_path(conn, :show, book))
      {:error, changeset} ->
        render(conn, "edit.html", book: book, changeset: changeset)
    end
  end

/web/templates/book/form.html.eex

  <div class="form-group">
    <%= label f, "Authors", class: "control-label" %>
    <%= multiple_select f, :people_ids, @people, class: "form-control" %>
    <%= error_tag f, :person_id %>
  </div>

I’m only learning Phoenix and Elixir so I’m probably missing something pretty essential. Could someone please explain what is wrong with this code?

OK. Just realized it’s only a question of on_replace: :delete option to many_to_many in the model. BTW, I was lead astray by the verbose explanation which accompanied the error message, maybe it might be useful to change it a bit?

After seeing the error (defined in this file), I agree that the explanation is a bit confusing. The term “missing data” doesn’t quite convey anything.

One thing that I think can make it better is to add an explanation that such message was raised because the :on_replace option is equal to :raise, which is the default if we don’t define it. A note on changing it is already explained on the last paragraph of the error message, but I think the fact that it was due to the :raise option could use some more exposure.

2 Likes

Please send a pull request if the error message or documentation could be improved. :slight_smile:

1 Like

Already done.

Whoops, seems like @expilo and I put up a PR at roughly the same time :sweat_smile: My bad for not notifying. Do let me know if I need to close it.

At least there are more ideas to choose from :wink: I will gladly close mine as it’s a rather naive explanation which certainly has to be corrected.