From 05f60e8a19e37e9b3807e2ecf67b537c9decb654 Mon Sep 17 00:00:00 2001 From: Tom Conroy Date: Tue, 25 Feb 2025 10:40:51 -0500 Subject: [PATCH] Allow suppressing warning when preloading data with missing keys --- lib/ecto/repo.ex | 4 ++++ lib/ecto/repo/preloader.ex | 20 ++++++++++++++------ test/ecto/repo_test.exs | 20 ++++++++++++++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/lib/ecto/repo.ex b/lib/ecto/repo.ex index 54c3588a18..0b63e69f20 100644 --- a/lib/ecto/repo.ex +++ b/lib/ecto/repo.ex @@ -1188,6 +1188,10 @@ defmodule Ecto.Repo do * `:on_preloader_spawn` - when preloads are done in parallel, this function will be called in the processes that perform the preloads. This can be useful for context propagation for traces. + * `:warn_if_nil_keys` - When set to `false`, suppresses the warning + when preloading associations where the parent's association key is nil. + Useful when working with unpersisted data (e.g., in changesets). + Defaults to `true`. See the ["Shared options"](#module-shared-options) section at the module documentation for more options. diff --git a/lib/ecto/repo/preloader.ex b/lib/ecto/repo/preloader.ex index 376decc0b5..d918df00ca 100644 --- a/lib/ecto/repo/preloader.ex +++ b/lib/ecto/repo/preloader.ex @@ -251,38 +251,46 @@ defmodule Ecto.Repo.Preloader do defp fetch_ids(structs, module, assoc, {_adapter_meta, opts}) do %{field: field, owner_key: owner_key, cardinality: card} = assoc force? = Keyword.get(opts, :force, false) + warn_if_nil_keys? = Keyword.get(opts, :warn_if_nil_keys, true) - Enum.reduce structs, {[], [], []}, fn + Enum.reduce(structs, {[], [], []}, fn nil, acc -> acc + struct, {fetch_ids, loaded_ids, loaded_structs} -> assert_struct!(module, struct) %{^owner_key => id, ^field => value} = struct loaded? = Ecto.assoc_loaded?(value) and not force? - if loaded? and is_nil(id) and not Ecto.Changeset.Relation.empty?(assoc, value) do - Logger.warning """ + if loaded? and is_nil(id) and not Ecto.Changeset.Relation.empty?(assoc, value) and warn_if_nil_keys? do + Logger.warning(""" association `#{field}` for `#{inspect(module)}` has a loaded value but \ its association key `#{owner_key}` is nil. This usually means one of: * `#{owner_key}` was not selected in a query * the struct was set with default values for `#{field}` which now you want to override - If this is intentional, set force: true to disable this warning - """ + If you want to override the data, set force: true. + + If you are intentionally preloading data that has not yet been committed (e.g. a new struct + with id: nil), you can set warn_if_nil_keys: false to disable this warning. + """) end cond do card == :one and loaded? -> {fetch_ids, [id | loaded_ids], [value | loaded_structs]} + card == :many and loaded? -> {fetch_ids, [{id, length(value)} | loaded_ids], value ++ loaded_structs} + is_nil(id) -> {fetch_ids, loaded_ids, loaded_structs} + true -> {[id | fetch_ids], loaded_ids, loaded_structs} end - end + end) end defp fetch_query(ids, assoc, _repo_name, query, _prefix, related_key, _take, _tuplet) diff --git a/test/ecto/repo_test.exs b/test/ecto/repo_test.exs index 56fd4be402..53b1b51973 100644 --- a/test/ecto/repo_test.exs +++ b/test/ecto/repo_test.exs @@ -2007,6 +2007,26 @@ defmodule Ecto.RepoTest do TestRepo.preload(%MySchema{id: 1}, children: intersect_all(query, ^query)) end end + + test "suppresses nil key warnings with warn_if_nil_keys option" do + parent = %MySchema{id: nil, children: [%MySchemaChild{a: "child1"}, %MySchemaChild{a: "child2"}]} + + log = ExUnit.CaptureLog.capture_log(fn -> + TestRepo.preload(parent, :children) + end) + + assert log =~ "association `children` for" + assert log =~ "its association key `id` is nil" + + log = ExUnit.CaptureLog.capture_log(fn -> + TestRepo.preload(parent, :children, warn_if_nil_keys: false) + end) + refute log =~ "association `children` for" + + result = TestRepo.preload(parent, :children, warn_if_nil_keys: false) + assert length(result.children) == 2 + assert Enum.map(result.children, & &1.a) == ["child1", "child2"] + end end describe "checkout" do