Skip to content

resetting preloaded parents on update doesnt happen if parent is nil #4669

@tom-carscan

Description

@tom-carscan

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions