From e9fbe72ad3a3830b5acfddf43f784cf808fd54dd Mon Sep 17 00:00:00 2001 From: Artem Baikov Date: Wed, 1 May 2024 15:34:46 +0300 Subject: [PATCH] Allow cast schemaless embeds --- lib/ecto/changeset/relation.ex | 16 +++++++++ test/ecto/changeset/schemaless_test.exs | 44 +++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 test/ecto/changeset/schemaless_test.exs diff --git a/lib/ecto/changeset/relation.ex b/lib/ecto/changeset/relation.ex index 1cc76c98db..b9e6e99846 100644 --- a/lib/ecto/changeset/relation.ex +++ b/lib/ecto/changeset/relation.ex @@ -103,6 +103,14 @@ defmodule Ecto.Changeset.Relation do end end + def cast(%{related: nil} = relation, owner, params, current, on_cast) do + fun = &do_cast(relation, owner, &1, &2, &3, &4, on_cast) + + with :error <- cast_or_change(relation, params, current, fn _ -> [] end, fn _ -> [] end, fun) do + {:error, {"is invalid", [type: expected_type(relation)]}} + end + end + def cast(%{related: mod} = relation, owner, params, current, on_cast) do pks = mod.__schema__(:primary_key) fun = &do_cast(relation, owner, &1, &2, &3, &4, on_cast) @@ -125,6 +133,14 @@ defmodule Ecto.Changeset.Relation do do_cast(meta, owner, params, struct, allowed_actions, idx, on_cast) end + defp do_cast(%{related: nil} = relation, _owner, params, nil = _struct, allowed_actions, idx, on_cast) do + {:ok, + relation + |> apply_on_cast(on_cast, %{}, params, idx) + |> put_new_action(:insert) + |> check_action!(allowed_actions)} + end + defp do_cast(relation, owner, params, nil = _struct, allowed_actions, idx, on_cast) do {:ok, relation diff --git a/test/ecto/changeset/schemaless_test.exs b/test/ecto/changeset/schemaless_test.exs new file mode 100644 index 0000000000..70ee623b60 --- /dev/null +++ b/test/ecto/changeset/schemaless_test.exs @@ -0,0 +1,44 @@ +defmodule Ecto.Changeset.SchemalessTest do + use ExUnit.Case, async: true + + defmodule Author do + import Ecto.Changeset + + @types %{ + name: :string, + age: :integer, + book: {:embed, Ecto.Embedded.init(cardinality: :one, field: :book)} + } + + @keys Map.keys(@types) -- [:book] + + @book_types %{ + id: :integer, + name: :string + } + + @book_keys Map.keys(@book_types) + + def build(entity, attrs) do + entity + |> changeset(attrs) + |> apply_action(:validate) + end + + def changeset(entity, attrs) do + {entity, @types} + |> cast(attrs, @keys) + |> cast_embed(:book, with: &book_changeset/2) + end + + def book_changeset(entity, attrs) do + {entity, @book_types} + |> cast(attrs, @book_keys) + end + end + + test "successfully casts schemaless embed" do + {:ok, data} = Author.build(%{}, %{name: "Jane", book: %{id: 1, name: "title 1"}}) + assert %{name: "Jane", book: %{id: 1, name: "title 1"}} = data + end +end