I checked git history and I confirm that Repo.preload(user, :projects) was called.
I did investigation in many_to_may
and many_to_many on_replace do not have an option that is not delete. Maybe this behaviour is Ecto by design?
Thank you for writing this post, you helped me a lot with my side project!