I have a little sample project I’m using to try to figure things out. I have an album schema and a track schema. The album has a has_many association to the track.
My UI is built so that the user can add and remove track rows and then click save when they are done. I discovered tonight that when I remove ALL the tracks and pass the album struct into the schema’s changeset function, the changeset loses its changes to the track list. This appears to be because there are no longer any tracks in the tracks field on the form data params and the resulting changeset thinks that there are no changes.
This is my album schema. It is very simple:
schema "albums" do
field :name, :string
field :artist, :string
field :genre, :string
field :rating, :integer
has_many(:tracks, Track,
foreign_key: :album_id,
on_replace: :delete
)
timestamps()
end
@doc false
def changeset(album, attrs) do
album
|> cast(attrs, [:name, :artist, :genre, :rating])
|> cast_assoc(:tracks, with: &Track.changeset/2)
|> validate_required([:name, :rating, :genre])
end
end
This is my track schema. The temp_id is to allow me to add and delete records in memory.
schema "tracks" do
field :name, :string
field :position, :integer
field :temp_id, :string, virtual: true
belongs_to :album, RecordStore.Albums.Album
timestamps()
end
@doc false
def changeset(track, attrs) do
track
|> cast(attrs, [:name, :position, :temp_id])
|> validate_required([:name, :position])
end
end
Again, it seems to work fine until I remove ALL the tracks. If I remove all the tracks, and then make another change on the form to fire the phx-change event and rebuild the changeset, all the tracks jump back in. If I remove all except one it works as expected.
Looking at the form params, there is no key for tracks once they are all removed.
One solution that works, but feels yucky, is to check if there are no tracks in the form params in the phx-change and the phx-submit events and then manually put an empty map into the tracks field.
Contemplating this, I guess the changeset functions assume that an absence of tracks in the form params means there were no changes. Manually sticking an empty map into the tracks field on the params forces it to know that we now have an empty list of tracks. But that seems janky to me.