defmodule EctoPostgresEnum do @moduledoc false defmacro __using__(opts) do quote(bind_quoted: [opts: opts]) do schema = opts[:schema] || :public type = opts[:type] || raise "Option type (atom) is required" values = opts[:values] || raise "Option values (list) is required" values == Enum.uniq(values) || raise "Duplicates are not allowed in enum values" Enum.count(values) >= 2 || raise "Valid enums requires at least 2 different values" Enum.all?(values, &is_atom/1) || raise "All values must be atoms" is_atom(schema) || raise "Option schema must be atom" alias Ecto.Migration @behaviour Ecto.Type @__input_values__ Enum.map(values, &Atom.to_string/1) @__output_values__ values @__schema__ schema @__type__ if is_nil(@__schema__), do: type, else: :"#{@__schema__}.#{type}" @__valid_values__ @__input_values__ ++ @__output_values__ types = Enum.map_join(values, ", ", &"'#{&1}'") @__create_sql__ "CREATE TYPE #{type} AS ENUM (#{types})" @__drop_sql__ "DROP TYPE #{@__type__}" @doc """ Check if specifed argument is valid element of enum. """ @spec valid_value?(term) :: boolean defmacro valid_value?(value) do quote do unquote(value) in unquote(@__valid_values__) end end @spec cast(value) :: value when value: atom def cast(atom) when is_atom(atom) and valid_value?(atom), do: {:ok, atom} @spec cast(String.t()) :: atom def cast(binary) when is_binary(binary) and valid_value?(binary), do: {:ok, String.to_existing_atom(binary)} @spec cast(term) :: :error def cast(_term), do: :error @doc """ Creates database enum """ @spec create_db_enum :: :ok def create_db_enum, do: Migration.execute(@__create_sql__) @doc """ Drops database enum """ @spec drop_db_enum :: :ok def drop_db_enum, do: Migration.execute(@__drop_sql__) @spec dump(atom) :: {:ok, String.t()} def dump(atom) when is_atom(atom) and valid_value?(atom), do: {:ok, Atom.to_string(atom)} @spec dump(value) :: value when value: String.t() def dump(string) when is_binary(string) and valid_value?(string), do: {:ok, string} @spec dump(term) :: :error def dump(_term), do: :error @spec load(value) :: {:ok, value} when value: atom def load(value) when is_binary(value) and valid_value?(value), do: {:ok, value} @spec load(term) :: :error def load(_value), do: :error @doc """ Returns database enum type """ @spec type :: atom def type, do: @__type__ end end end