-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
Elixir version
1.18.3
Database and Version
PostgreSQL 14.15
Ecto Versions
3.13.2
Database Adapter and Versions (postgrex, myxql, etc)
postgrex 0.21.1
Current behavior
Ecto.Repo.Schema
defp reset_parent?(changes, data, assoc) do
%{field: field, owner_key: owner_key, related_key: related_key} = assoc
with %{^owner_key => owner_value} <- changes,
%{^field => %{^related_key => related_value}} when owner_value != related_value <- data do
true
else
_ -> false
end
end
This function is used to check if preloaded parents need to be reset when you change the id the child is associated to. but this does not work if the preloaded association was empty.
To reproduce this:
Let's say you have a Child which has a parent_id that refers to Parent. So the schema for Child would have a belongs_to(Parent)
By default you will have something like this if you were to look at a Child
%Child{ parent_id: 2, parent: #Ecto.Association.NotLoaded<association :parent is not loaded> }
you can use ecto to preload that association which would give the following:
%Child{ parent_id: 2, parent: %Parent{id: 2} }
If you were to use this child with preloaded parent in an update statement like this:
%Child{ parent_id: 2, parent: %Parent{id: 2} } |> Child.changeset(%{parent_id: 3}) |> My.Repo.update()
Then the result would be:
%Child{ parent_id: 3, parent: #Ecto.Association.NotLoaded<association :parent is not loaded> }
Because the reset_parent? function would notice that the parent_id (3) does not the id in the parent (which is still 2 from the preload) which then resets that.
So far so good.
Now the problem:
If we do this same thing with a Child that does not have a parent:
%Child{ parent_id: nil, parent: #Ecto.Association.NotLoaded<association :parent is not loaded> }
So we preload it
%Child{ parent_id: nil, parent: nil }
And then change this
%Child{ parent_id: nil, parent: nil } |> Child.changeset(%{parent_id: 3}) |> My.Repo.update()
then the result is
%Child{ parent_id: 3, parent: nil }
Because it cannot match the part in the reset_parent? function where he checks if the id's are different.
This also causes the preload functions to not work for this because it has already been preloaded, it just contains the old (empty) value.
Expected behavior
I would expect the association to be reset in this case even if it started from nil.