Skip to content

Instantly share code, notes, and snippets.

@elbow-jason
Created September 20, 2019 00:16
Show Gist options
  • Save elbow-jason/85c1f2c2a48890bf0905916067650058 to your computer and use it in GitHub Desktop.
Save elbow-jason/85c1f2c2a48890bf0905916067650058 to your computer and use it in GitHub Desktop.
alias Ecto.Changeset
defmodule Poly1 do
use Ecto.Schema
embedded_schema do
field(:key1, :string)
end
def changeset(poly, params) do
Changeset.cast(poly, params, [:key1])
end
end
defmodule Poly2 do
use Ecto.Schema
embedded_schema do
field(:key1, :string)
end
def changeset(poly, params) do
Changeset.cast(poly, params, [:key1])
end
end
defmodule MyPoly.EctoType do
@behaviour Ecto.Type
def type, do: :map
def cast(map) when is_map(map) do
with(
{:ok, kind} <- fetch_kind(map),
{:ok, poly_module} <- fetch_poly_module(kind),
{:ok, casted} <- cast_poly(poly_module, map)
) do
{:ok, casted}
else
{:error, {:invalid_kind, kind}} ->
{:error, [invalid_kind: kind]}
{:error, {:invalid_poly, changeset}} ->
{:error, flatten_errors(changeset)}
end
end
def dump(%poly_module{} = poly) do
kind = poly_module_to_kind_int(poly_module)
map =
poly
|> Map.from_struct()
|> Map.put(:__kind__, kind)
{:ok, map}
end
def dump(_), do: :error
def load(map) when is_map(map) do
with(
{:ok, kind} <- fetch_kind(map),
{:ok, poly_module} <- fetch_poly_module(kind),
{:ok, casted} <- cast_poly(poly_module, map)
) do
{:ok, casted}
else
_ -> :error
end
end
def load(_), do: :error
defp fetch_kind(map) do
types = %{
kind: :integer,
__kind__: :integer
}
{%{}, types}
|> Changeset.cast(map, [:kind, :__kind__])
|> Changeset.apply_action(:insert)
|> case do
{:ok, %{kind: kind}} ->
{:ok, kind}
{:ok, %{__kind__: kind}} ->
{:ok, kind}
{:ok, %{}} ->
{:error, {:invalid_kind, nil}}
{:error, changeset} ->
invalid_kind_from_changeset(changeset)
end
end
defp invalid_kind_from_changeset(changeset) do
kind = Changeset.get_change(changeset, :kind)
{:error, {:invalid_kind, kind}}
end
defp flatten_errors(changeset) do
changeset
|> Changeset.traverse_errors(fn {message, opts} ->
{message, opts}
end)
|> Enum.into([])
end
defp poly_module_to_kind_int(Poly1), do: 1
defp poly_module_to_kind_int(Poly2), do: 2
defp fetch_poly_module(1), do: {:ok, Poly1}
defp fetch_poly_module(2), do: {:ok, Poly2}
defp fetch_poly_module(k), do: {:error, {:invalid_kind, k}}
defp cast_poly(poly_module, map) do
# this cast assumes there is a changeset/2.
# Might want a behaviour for poly to ensure that's the case.
poly_module.__struct__()
|> poly_module.changeset(map)
|> Changeset.apply_action(:insert)
|> case do
{:ok, poly} ->
{:ok, poly}
{:error, changeset} ->
errors = flatten_errors(changeset)
{:error, {:invalid_poly, errors}}
end
end
end
defmodule Parent do
use Ecto.Schema
embedded_schema do
embeds_one(:some_poly, MyPoly.EctoType)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment