From 19df45f7e2dfcb0cc4634ad73e25e936ae2ef607 Mon Sep 17 00:00:00 2001 From: Bernardo Amorim <828081+bamorim@users.noreply.github.com> Date: Tue, 7 May 2024 10:59:52 +0100 Subject: [PATCH] Add :defaults_to_struct option to embeds_one (#4410) --- lib/ecto/schema.ex | 16 +++++++++++++++- test/ecto/embedded_test.exs | 15 +++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/ecto/schema.ex b/lib/ecto/schema.ex index 151bbb3dd7..4d593369c9 100644 --- a/lib/ecto/schema.ex +++ b/lib/ecto/schema.ex @@ -1648,6 +1648,12 @@ defmodule Ecto.Schema do selecting the whole struct in a query, such as `from p in Post, select: p`. Defaults to `true`. + * `:defaults_to_struct` - When true, the field will default to the initialized + struct instead of nil, the same you would get from something like `%Order.Item{}`. + One important thing is that if the underlying data is explicitly nil when loading + the schema, it will still be loaded as nil, similar to how `:default` works in fields. + Defaults to `false`. + ## Examples defmodule Order do @@ -2223,11 +2229,19 @@ defmodule Ecto.Schema do Module.put_attribute(mod, :ecto_changeset_fields, {name, {:assoc, struct}}) end - @valid_embeds_one_options [:on_replace, :source, :load_in_query] + @valid_embeds_one_options [:on_replace, :source, :load_in_query, :defaults_to_struct] @doc false def __embeds_one__(mod, name, schema, opts) when is_atom(schema) do check_options!(opts, @valid_embeds_one_options, "embeds_one/3") + + opts = + if Keyword.get(opts, :defaults_to_struct) do + Keyword.put(opts, :default, schema.__schema__(:loaded)) + else + opts + end + embed(mod, :one, name, schema, opts) end diff --git a/test/ecto/embedded_test.exs b/test/ecto/embedded_test.exs index 000164aa3c..162107b6c2 100644 --- a/test/ecto/embedded_test.exs +++ b/test/ecto/embedded_test.exs @@ -34,6 +34,15 @@ defmodule Ecto.EmbeddedTest do end end + defmodule Settings do + use Ecto.Schema + + embedded_schema do + field :dark_mode, :boolean, default: false + embeds_one :default_post, Post, defaults_to_struct: true + end + end + test "__schema__" do assert Author.__schema__(:embeds) == [:profile, :post, :posts] @@ -63,6 +72,12 @@ defmodule Ecto.EmbeddedTest do assert %UUIDSchema{uuid: ^uuid, authors: [%Author{}]} = Ecto.embedded_load(UUIDSchema, %{"uuid" => uuid, "authors" => [%{}]}, :json) + assert %Settings{dark_mode: false, default_post: %Post{}} = + Ecto.embedded_load(Settings, %{}, :json) + + assert %Settings{dark_mode: false, default_post: nil} = + Ecto.embedded_load(Settings, %{"default_post" => nil}, :json) + assert_raise ArgumentError, ~s[cannot load `"ABC"` as type Ecto.UUID for field `uuid` in schema Ecto.EmbeddedTest.UUIDSchema], fn ->