Created
August 12, 2020 13:35
-
-
Save davydog187/0bec728c8f61218e9390aa43266b2acf to your computer and use it in GitHub Desktop.
Enum type
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
defmodule App.Ecto.Atom do | |
@moduledoc """ | |
Generates an Ecto custom type for atoms. | |
e.g. | |
defmodule MyType do | |
use App.Ecto.Atom, values: [:one, :two, :three] | |
end | |
Then you can use it an an Ecto Custom type | |
defmodule MySchema do | |
schema :some_table do | |
field(:foo, MyType) | |
end | |
end | |
Ecto.Atom will make sure Strings are properly casted | |
""" | |
alias App.Ecto.Atom, as: Parent | |
defmacro __using__(opts) do | |
values = Keyword.fetch!(opts, :values) | |
quote location: :keep do | |
use Ecto.Type | |
@values unquote(values) | |
@before_compile Parent | |
@impl Ecto.Type | |
def type, do: :string | |
@impl Ecto.Type | |
def cast(data), do: Parent.cast_atom(__MODULE__, transform(data)) | |
@impl Ecto.Type | |
def load(data), do: Parent.load_atom(__MODULE__, data) | |
@impl Ecto.Type | |
def dump(data), do: Parent.dump_atom(__MODULE__, data) | |
def transform(data), do: data | |
@spec values() :: [__MODULE__.t()] | |
def values, do: @values | |
defoverridable transform: 1 | |
end | |
end | |
defmacro __before_compile__(env) do | |
values = Module.get_attribute(env.module, :values) | |
Enum.each(values, fn value -> | |
unless is_atom(value) do | |
raise "#{__MODULE__} expects :values to be atoms, got: #{inspect(value)}" | |
end | |
end) | |
if Enum.count(values) != Enum.count(MapSet.new(values)) do | |
description = """ | |
Ecto.Atom cannot contain duplicate values! | |
#{inspect(env.module)} | |
#{inspect(values)} | |
""" | |
raise CompileError, description: description | |
end | |
casting_ast = | |
for value <- values do | |
quote do | |
def __cast_atom__(unquote(value)), do: {:ok, unquote(value)} | |
def __cast_atom__(unquote(to_string(value))), do: {:ok, unquote(value)} | |
end | |
end | |
quote location: :keep do | |
@type t :: unquote(Enum.reduce(values, &{:|, [], [&1, &2]})) | |
unquote(casting_ast) | |
def __cast_atom__(_), do: :error | |
end | |
end | |
def cast_atom(module, data) do | |
case module.__cast_atom__(data) do | |
{:ok, atom} -> {:ok, atom} | |
:error -> {:error, [invalid_atom: data, valid_options: module.values()]} | |
end | |
end | |
@spec load_atom(atom, any) :: any | |
def load_atom(module, data) do | |
module.__cast_atom__(data) | |
end | |
def dump_atom(module, data) do | |
case module.__cast_atom__(data) do | |
{:ok, atom} -> {:ok, to_string(atom)} | |
:error -> :error | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment