Current section
Files
Jump to
Current section
Files
lib/ecto_enum.ex
defmodule Ecto.Enum do
@moduledoc """
Provides `defenum/2` macro for defining an Enum Ecto type.
"""
@doc """
Defines an enum custom `Ecto.Type`.
It can be used like any other `Ecto.Type` by passing it to a field in your model's
schema block. For example:
import Ecto.Enum
defenum StatusEnum, registered: 0, active: 1, inactive: 2, archived: 3
defmodule User do
use Ecto.Model
schema "users" do
field :status, StatusEnum
end
end
In the above example, the `:status` will behave like an enum and will allow you to
pass an `integer`, `atom` or `string` to it. This applies to saving the model,
invoking `Ecto.Changeset.cast/4`, or performing a query on the status field. Let's
do a few examples:
iex> user = Repo.insert!(%User{status: 0})
iex> Repo.get(User, user.id).status
:registered
iex> %{changes: changes} = cast(%User{}, %{"status" => "Active"}, ~w(status), [])
iex> changes.status
:active
iex> from(u in User, where: u.status == :registered) |> Repo.all() |> length
1
Passing a value that the custom Enum type does not recognize will result in an error.
iex> Repo.insert!(%User{status: :none})
** (Elixir.Ecto.Enum.Error) :none is not a valid enum value
The enum type `StatusEnum` will also have a reflection function for inspecting the
enum map in runtime.
iex> StatusEnum.__enum_map__(:status)
[registered: 0, active: 1, inactive: 2, archived: 3]
"""
defmodule Error do
defexception [:message]
def exception(value) do
msg = "#{inspect value} is not a valid enum value"
%__MODULE__{message: msg}
end
end
defmacro defenum(module, enum_kw) when is_list(enum_kw) do
enum_map = for {atom, int} <- enum_kw, into: %{}, do: {int, atom}
enum_map = Macro.escape(enum_map)
enum_map_string = for {atom, int} <- enum_kw, into: %{}, do: {Atom.to_string(atom), int}
enum_map_string = Macro.escape(enum_map_string)
quote do
defmodule unquote(module) do
@behaviour Ecto.Type
def type, do: :integer
def cast(term) do
check_value!(term)
Ecto.Enum.cast(term, unquote(enum_map))
end
def load(int) when is_integer(int) do
{:ok, unquote(enum_map)[int]}
end
def dump(term) do
check_value!(term)
Ecto.Enum.dump(term, unquote(enum_kw), unquote(enum_map_string))
end
# Reflection
def __enum_map__(:status), do: unquote(enum_kw)
defp check_value!(atom) when is_atom(atom) do
unless unquote(enum_kw)[atom] do
raise Ecto.Enum.Error, atom
end
end
defp check_value!(string) when is_binary(string) do
unless unquote(enum_map_string)[string] do
raise Ecto.Enum.Error, string
end
end
defp check_value!(int) when is_integer(int) do
unless unquote(enum_map)[int] do
raise Ecto.Enum.Error, int
end
end
end
end
end
def cast(atom, _enum_map) when is_atom(atom), do: {:ok, atom}
def cast(string, _enum_map) when is_binary(string), do: {:ok, String.to_atom(string)}
def cast(int, enum_map) when is_integer(int) do
atom = enum_map[int]
{:ok, atom}
end
def cast(_term), do: :error
def dump(int, _enum_kw, _enum_map_string) when is_integer(int) do
{:ok, int}
end
def dump(atom, enum_kw, _enum_map_string) when is_atom(atom) do
{:ok, enum_kw[atom]}
end
def dump(string, _enum_kw, enum_map_string) when is_binary(string) do
{:ok, enum_map_string[string]}
end
def dump(_), do: :error
end