sb logoToday I Learned

Taming data with Ecto.Enum and Ecto.Type

A coworker and I discussed about taking advantage of Ecto.Enum and Ecto.Type instead of having one more dependency.

The schema:

defmodule Blog.Category do
  use Blog.Schema

  schema "categories" do
    field(:name, Ecto.Enum, [:til, :elixir, :ecto])
  end
end

Divide & Conquer with Reflections and Ecto.Type.load/3:

iex> type = Blog.Category.__schema__(:type, :name)
{:parameterized, Ecto.Enum,
 %{
   mappings: [til: "til", elixir: "elixir", ecto: "ecto"],
   on_cast: %{"til" => :til, "elixir" => :elixir, "ecto" => :ecto},
   on_dump: %{til: "til", elixir: "elixir", ecto: "ecto"},
   on_load: %{"til" => :til, "elixir" => :elixir, "ecto" => :ecto},
   type: :string
 }}
iex> Ecto.Type.load(type, "unknown")
:error
iex> Ecto.Type.load(type, "ecto")
{:ok, :ecto}
iex> Ecto.Type.load(type, :ecto)
:error

In the meantime:

iex> Ecto.Enum.values(Blog.Category, :name)
[:til, :elixir, :ecto]
iex> Ecto.Enum.dump_values(Blog.Category, :name)
["til", "elixir", "ecto"]
iex> Ecto.Enum.mappings(Blog.Category, :name)
[til: "til", elixir: "elixir", ecto: "ecto"]

Also, now we have the same API ecto_enum:

iex> valid? = fn list, value -> Enum.any?(list, fn item -> item == value end) end
#Function<43.40011524/2 in :erl_eval.expr/5>
iex> categories = Ecto.Enum.dump_values(Blog.Category, :name)
["til", "elixir", "ecto"]
iex> category = "unknown"
"unknown"
iex> if valid?.(categories, category), do: {:ok, String.to_existing_atom(category)}, else: :error
:error
iex> category = "ecto"
"ecto"
iex> if valid?.(categories, category), do: {:ok, String.to_existing_atom(category)}, else: :error
{:ok, :ecto}

Cool!