Polymorphic embed in Ecto

@alexcastano Of course it’s possible, but ecto itself does not helps in complex polymorphic use cases, so you would need to write it yourself.

First of all you would need to have your JSON like:

{"data": …, "type": "your_type_name"}

For it you would need to create a custom Ecto.Type. For each callback you need to check type field and based on it you would need to create n number of modules which also implements Ecto.Type.

Let’s say:

defmodule MyApp.EctoTypes.MainJSON do
  use Ecto.Type

  def ecto_callback(%{data: data, type: type}) do
    with {:ok, result} <- type |> find_module() |> :apply(:ecto_callback, [data]) do
      %{data: result, type: type}
    end
  end

  defp find_module("first_type"), do: MyApp.EctoTypes.FirstType
  defp find_module("second_type"), do: MyApp.EctoTypes.SecondType
  defp find_module("third_type"), do: MyApp.EctoTypes.ThirdType
end

defmodule MyApp.EctoTypes.FirstType do
  # no need to use Ecto.Type here
  # as this those "sub types" would be used only for Kernel.apply/3
  # in order to simply separate code for each type

  def ecto_callback(data) do
    # …
  end
end

In such way you can simply write a code for each type. You only need to implement each Ecto.Type required callback (cast, dump and load if I remember correctly).

If you do this all you need to do (like migrations) is exactly the same as with embeds_many or embeds_one. I wrote all from memory, but it should work even with arrays.

defmodule MyApp.MyContext.MySchema do
  alias MyApp.EctoTypes.MainJSON

  schema "table_name" do
    …
    field(:field_name, {:array, MainJSON})
    …
  end
end

The only difference is that you no longer need to call cast_embed and related functions as everything would be handled in cast.