diff --git a/apps/one_piece_commanded/CHANGELOG.md b/apps/one_piece_commanded/CHANGELOG.md index 498de6b..b142f31 100644 --- a/apps/one_piece_commanded/CHANGELOG.md +++ b/apps/one_piece_commanded/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## v0.21.1 - 2024-11-26 + +- Fix casting structs in `OnePiece.Commanded.ValueObject`. Related to https://github.com/elixir-ecto/ecto/issues/4168 + ## v0.21.0 - 2024-11-02 - Added `OnePiece.Commanded.Enum` module. diff --git a/apps/one_piece_commanded/lib/one_piece/commanded/value_object.ex b/apps/one_piece_commanded/lib/one_piece/commanded/value_object.ex index 829a893..5e274ea 100644 --- a/apps/one_piece_commanded/lib/one_piece/commanded/value_object.ex +++ b/apps/one_piece_commanded/lib/one_piece/commanded/value_object.ex @@ -140,33 +140,27 @@ defmodule OnePiece.Commanded.ValueObject do changeset = message - |> Changeset.cast(attrs, fields -- embeds) + |> Changeset.cast(from_struct(attrs), fields -- embeds) |> Changeset.validate_required(struct_module.__enforced_keys__() -- embeds) Enum.reduce( embeds, changeset, - &cast_embed(&1, &2, struct_module, attrs) + &cast_embed(&1, &2, struct_module) ) end - defp cast_embed(field, changeset, struct_module, attrs) do - case is_struct(attrs[field]) do - false -> - Changeset.cast_embed(changeset, field, required: struct_module.__enforced_keys__?(field)) - - true -> - # credo:disable-for-next-line Credo.Check.Design.TagTODO - # TODO: Validate that the struct is of the correct type. - # It may be the case that you passed a completely different struct as the value. We could `cast_embed` - # always and fix the `Changeset.cast(attrs, fields -- embeds)` by converting the `attrs` into a map. But it - # would be a bit more expensive since it will run the casting for a field that was already casted. - # Checking the struct types MAY be enough but taking into consideration `embeds_many` could complicated - # things. For now, we'll just assume that the user knows what they're doing. - Changeset.put_change(changeset, field, attrs[field]) - end + defp cast_embed(field, changeset, struct_module) do + Changeset.cast_embed(changeset, field, required: struct_module.__enforced_keys__?(field)) + end + + defp from_struct(value) when is_struct(value) do + # https://github.com/elixir-ecto/ecto/issues/4168 + Map.from_struct(value) end + defp from_struct(value), do: value + defp apply_changeset(struct_module, attrs) do struct(struct_module) |> struct_module.changeset(attrs) diff --git a/apps/one_piece_commanded/mix.exs b/apps/one_piece_commanded/mix.exs index 3b6c96a..4f0c5f5 100644 --- a/apps/one_piece_commanded/mix.exs +++ b/apps/one_piece_commanded/mix.exs @@ -2,7 +2,7 @@ defmodule OnePiece.Commanded.MixProject do use Mix.Project @app :one_piece_commanded - @version "0.21.0" + @version "0.21.1" @elixir_version "~> 1.13" @source_url "https://github.com/straw-hat-team/beam-monorepo" diff --git a/apps/one_piece_commanded/test/one_piece/commanded/value_object_test.exs b/apps/one_piece_commanded/test/one_piece/commanded/value_object_test.exs index be1cd5f..bbd0deb 100644 --- a/apps/one_piece_commanded/test/one_piece/commanded/value_object_test.exs +++ b/apps/one_piece_commanded/test/one_piece/commanded/value_object_test.exs @@ -21,9 +21,17 @@ defmodule OnePiece.Commanded.ValueObjectTest do TestSupport.MessageThree.new(%{target: %{title: "Hello, World!"}}) end - test "bypass casting structs" do + test "casting structs" do assert {:ok, %TestSupport.MessageThree{target: %TestSupport.MessageOne{title: "Hello, World!"}}} = TestSupport.MessageThree.new(%{target: %TestSupport.MessageOne{title: "Hello, World!"}}) + + assert {:ok, + %TestSupport.MessageFour{ + targets: [%TestSupport.MessageThree{target: %TestSupport.MessageOne{title: "Hello, World!"}}] + }} = + TestSupport.MessageFour.new(%{ + targets: [%TestSupport.MessageThree{target: %TestSupport.MessageOne{title: "Hello, World!"}}] + }) end test "validates casting embed fields with a wrong value" do diff --git a/apps/one_piece_commanded/test/support/test_support.ex b/apps/one_piece_commanded/test/support/test_support.ex index eb05a94..8068db0 100644 --- a/apps/one_piece_commanded/test/support/test_support.ex +++ b/apps/one_piece_commanded/test/support/test_support.ex @@ -43,6 +43,17 @@ defmodule TestSupport do end end + defmodule MessageFour do + @moduledoc false + + use OnePiece.Commanded.ValueObject + + @enforce_keys [:targets] + embedded_schema do + embeds_many(:targets, MessageThree) + end + end + defmodule MyEntityOne do @moduledoc false