polymorphic_embed icon indicating copy to clipboard operation
polymorphic_embed copied to clipboard

Polymorphic embeds are not kept as changesets but are turned into structs - as opposed to what happens with ordinary embeds

Open maxmarcon opened this issue 1 year ago • 6 comments

Observed behavior: When I build a changeset with ordinary embeds, these embeds stay changesets. When I build the changesets with polymorphic embeds, the embeds are turned into structs if they're valid.

Expected behavior: I would expect polymorphic embeds to be kept as changeset until I call apply_changes or apply_action on the parent changeset, just as normal embeds do.

Ecto version: 3.9.4 polymorphic_embed version: 3.0.5

Here's a working example:

Mix.install([{:ecto, "~> 3.9.4"}, {:polymorphic_embed, "~> 3.0.5"}])

defmodule ChildSchema do
  use Ecto.Schema
  import Ecto.Changeset

  embedded_schema do
    field(:name, :string)
    field(:age, :integer)
  end

  def changeset(source \\ %__MODULE__{}, changes) do
    cast(source, changes, [:name, :age])
  end
end

defmodule ParentSchema do
  use Ecto.Schema
  import Ecto.Changeset

  embedded_schema do
    embeds_many(:children, ChildSchema)
  end

  def changeset(source \\ %__MODULE__{}, changes) do
    cast(source, changes, [])
    |> cast_embed(:children)
  end
end

defmodule ParentSchemaWithPolymorphicChildren do
  use Ecto.Schema
  import PolymorphicEmbed
  import Ecto.Changeset

  embedded_schema do
    polymorphic_embeds_many(:children,
      types: [
        child_schema: ChildSchema
      ],
      on_replace: :delete,
      on_type_not_found: :raise
    )
  end

  def changeset(source \\ %__MODULE__{}, changes) do
    cast(source, changes, [])
    |> cast_polymorphic_embed(:children)
  end
end

ParentSchema.changeset(%{children: [%{name: "Tic", age: 10}, %{name: "Tac", age: 12}]})
|> IO.inspect(label: "changeset for schema with traditional embeds: embeds are still changesets")

ParentSchemaWithPolymorphicChildren.changeset(%{
  children: [
    %{name: "Tic", age: 10, __type__: "child_schema"},
    %{name: "Tac", age: 12, __type__: "child_schema"}
  ]
})
|> IO.inspect(
  label: "changeset for schema with polymorphic embeds: embeds are have already been applied"
)

Result of executing the above code:

changeset for schema with traditional embeds: embeds are still changesets

#Ecto.Changeset<
  action: nil,
  changes: %{
    children: [
      #Ecto.Changeset<
        action: :insert,
        changes: %{age: 10, name: "Tic"},
        errors: [],
        data: #ChildSchema<>,
        valid?: true
      >,
      #Ecto.Changeset<
        action: :insert,
        changes: %{age: 12, name: "Tac"},
        errors: [],
        data: #ChildSchema<>,
        valid?: true
      >
    ]
  },
  errors: [],
  data: #ParentSchema<>,
  valid?: true
>

changeset for schema with polymorphic embeds: changes in embeds have already been applied

#Ecto.Changeset<
  action: nil,
  changes: %{
    children: [
      %ChildSchema{
        id: "cd555105-4033-425f-b984-29f8c2062de9",
        name: "Tic",
        age: 10
      },
      %ChildSchema{
        id: "5c55473d-e1d5-4f76-96a9-b63685c55ca3",
        name: "Tac",
        age: 12
      }
    ]
  },
  errors: [],
  data: #ParentSchemaWithPolymorphicChildren<>,
  valid?: true
>

maxmarcon avatar Jan 27 '23 15:01 maxmarcon