Search code examples
many-to-manyphoenix-frameworkectoelixir

How to update relation of a model in Phoenix?


When I try to edit a book in my MyDb app I'm getting the following error message:

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

Here are the relevant snippets:

/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>

The comment under error message says I should include the entry primary key (ID) alongside the data if I want to update an existing entry but I can't figure out how.


Solution

  • It is necessary to use the option on_replace: :delete in books schema

     schema "books" do
        field :title, :string
        field :note, :string
        many_to_many :people, MyDb.Person, join_through: MyDb.BookPerson, on_replace: :delete
        timestamps()
      end