Created
September 20, 2019 00:16
-
-
Save elbow-jason/85c1f2c2a48890bf0905916067650058 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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