Fix `Changeset.changed?/2` for assocs
It now returns true whenever fetch_change? != :error. Previously, it returned
false when assoc was set to existing struct (as changeset.changes == %{}) and
errored when set to nil (due to nil.changes != %{} check in relation_changed?.
Fixes #4543.
We should fix it to handle nil because I think this code is unfortunately wrong. We could have an association that has not changed (i.e. it is an empty changeset).
Hi @josevalim thank you for looking into this. Note that handling nil is not the only issue — there is a valid case where action==:update && changes==%{}, e.g. when setting association to existing struct as in https://github.com/elixir-ecto/ecto/pull/4544/files#diff-8176128515609669c40d0d061249534c49e369abd7bc42c61cdbf5c148da388dR590
Yes, we need to check more cases. And potentialy make it recursive. But simply checking if the field is set is not enough.
Ping! :)
Sorry for the delay. I've been tied up with other priorities — I will try to move this forward till the end of this month...
@josevalim I am having some difficulty coming up with test cases that would fail with Changeset.changed? behaviour introduced in this PR (compared to current behaviour); it seems that Changeset functions for assoc/embed manipulation already prune most of empty assoc changesets, e.g.
{:ok, existing_profile} = TestRepo.insert(%Profile{name: "profile"})
%Author{profile: existing_profile}
|> Changeset.change()
|> Changeset.put_assoc(:profile, existing_profile |> Changeset.change(name: "aa"))
|> Changeset.put_assoc(:profile, existing_profile |> Changeset.change(name: "profile"))
already removes profile assoc changeset (as it would contain changes: %{}) from author changeset profile. Same goes for cast_assoc, and after quick inspection, I think also for has_many and has_one associations.
We could probably manipulate changeset struct directly, but we should probably test for scenarios that can results from using Changeset functions?