Hi, i’m struggling to find a way to preload an association inside a embedded schema from the base schema level. Below is an example of what i’m trying to achieve:
defmodule TestAssoc do
use Ecto.Schema
schema "test_assoc" do
field :name, :string
end
end
defmodule TestEmbedded do
use Ecto.Schema
alias TestAssoc
embedded_schema do
belongs_to :test_assoc, TestAssoc
end
end
defmodule Test.Repo do
use Ecto.Repo,
otp_app: :test,
adapter: Ecto.Adapters.Postgres
end
defmodule Test do
use Ecto.Schema
alias TestEmbedded
alias Test.Repo
schema "test" do
embeds_one :test_embedded, TestEmbedded
end
defp load_test do
%Test{}
|> Repo.all()
|> Repo.preload({:test_embedded, [:test_assoc]})
end
end
Is there any way to do this with embedded schemas?
I’ve seen a few people ask for that in the last handful of weeks. I’m really curious about the usecase behind that. Embeds are not bound by foreign key constraints, so this feels like something bound to get out of consistency.
At the moment I did a helper to do the recursive preload with fields that have embedded.
Is it okay to do this?
defmodule Test.Repo do
use Ecto.Repo,
otp_app: :test,
adapter: Ecto.Adapters.Postgres
def preload_with_embedded(struct_structs_or_nil, preloads, opts \\ [])
def preload_with_embedded(structs, preloads, opts) when is_list(structs) do
Enum.map(structs, fn struct ->
struct |> preload_with_embedded(preloads, opts)
end)
end
def preload_with_embedded(struct, preload, opts) when is_map(struct) and is_tuple(preload) do
{field, nested_preloads} = preload
struct_scope = Map.get(struct, field, nil)
if Ecto.assoc_loaded?(struct_scope) do
preload_with_embedded_handler(struct, field, nested_preloads, opts)
else
struct
|> preload(field, opts)
|> preload_with_embedded_handler(field, nested_preloads, opts)
end
end
def preload_with_embedded(struct, preloads, _opts) when is_map(struct) and is_list(preloads) do
Enum.reduce(preloads, struct, fn p, acc ->
acc |> preload_with_embedded(p)
end)
end
def preload_with_embedded(struct, preload, opts) when is_map(struct) and is_atom(preload) do
struct |> preload(preload, opts)
end
defp preload_with_embedded_handler(struct, field, nested_preloads, opts) do
case Map.get(struct, field, nil) do
ss when is_nil(ss) ->
nil
ss when is_map(ss) ->
Map.put(
struct,
field,
preload_with_embedded(ss, nested_preloads, opts)
)
ss when is_list(ss) ->
Map.put(
struct,
field,
Enum.map(ss, fn nested_struct ->
nested_struct |> preload_with_embedded(nested_preloads, opts)
end)
)
_ ->
nil
end
end
end
Here’s one use case: (I think) Using embedded schema ad a final staging check to validate egress data that is going to a 3rd party data consumer. Once you perform the validation, you shoot it off to the 3rd party api and never see it again.