Cast_assoc is not automatically handling foreign key

Hello readers :slight_smile:

I found this nice tutorial about a sorting list with liveview.

I’ve rebuilded it but it don’t work on my side. I have a contact list with contacts. But the cast assoc only ads the contact_id and not the list_id. I went through it a few hours but I don’t find the problem. What did I miss?

Contacts.update_list(socket.assigns.list, list_params) #=> {:error,
 #Ecto.Changeset<
   action: :update,
   changes: %{
     contact_lists: [
       #Ecto.Changeset<
         action: :insert,
         changes: %{position: 0, contact_id: 4},
         errors: [list_id: {"can't be blank", [validation: :required]}],
         data: #MyApp.Contacts.ContactList<>,
         valid?: false
       >
     ]
   },
   errors: [],
   data: #Huugo.Contacts.List<>,
   valid?: false
 >}
defmodule MyApp.Contacts.List do
  use Ecto.Schema
  import Ecto.Changeset

  alias MyApp.Contacts.ContactList

  schema "lists" do
    field :name, :string

    has_many :contact_lists, ContactList,
      preload_order: [asc: :position],
      on_replace: :delete

    has_many :contacts, through: [:contact_lists, :contact]

    timestamps()
  end

  def changeset(list, attrs) do
    dbg(list.contact_lists)

    list
    |> cast(attrs, [:name])
    |> validate_required([:name])
    |> cast_assoc(:contact_lists,
      with: &ContactList.changeset/3,
      sort_param: :contacts_order,
      drop_param: :contacts_delete
    )
  end
end

parameters:

%{
  "contact_lists" => %{"0" => %{"_persistent_id" => "0", "contact_id" => "4"}},
  "contacts_order" => ["0"],
  "name" => "List A"
}

save function

...
  def save_list(socket, :new_list, list_params) do
    case Contacts.create_list(list_params) do
      {:ok, _list} ->
        {:noreply,
         socket
         |> push_patch(to: ~p"/contacts")
         |> put_flash(:info, "List  created successfully")}

      {:error, %Ecto.Changeset{} = changeset} ->
        dbg(changeset)
        changeset = Map.put(changeset, :action, :new)

        {:noreply, assign_form(socket, changeset)}
    end
  end
...

Can you share your ContactList schema and changeset?

Just don’t require the :list_id in your ContactList changeset function.

Ecto will set it correctly when actually saving. Think about it, it’s not really possible for Ecto to add it during the cast_assoc because it may not actually exist if for example the list is to be created too.

1 Like

Thank you. That was it. I was thinking the same as you wrote, but when updating a list, Ecto would be aware of the ID. So i was thinking it first creates the list record and uses then the id for the changeset. But I see now the order of actions more clear.
Have a nice evening :slight_smile:

1 Like