Automatically Convert Ecto Atom to String


If you find yourself converting atoms into strings before creating an Ecto changeset, you can careate a custom type and offload it to Ecto.

Wrong

updated = %{status: :open}

old_schema
|> Model.changeset(%{
  status: updated.status |> Atom.to_string()
})
|> Repo.update()

Better

Instead, create a custom ecto type in utils/ecto/atom_type.ex:

defmodule Project.Utils.Ecto.AtomType do
  @moduledoc """
  Cast atom type to string when dumping to database; cast to atom when reading from database.
  """
  use Ecto.Type
  def type, do: :string
  def cast(value), do: {:ok, value}
  def load(value), do: {:ok, String.to_atom(value)}
  def dump(value) when is_atom(value), do: {:ok, Atom.to_string(value)}
  def dump(value) when is_binary(value), do: {:ok, value}
  def dump(_), do: :error
end

Then, make sure you specfy to use the custom type in your schema:

defmodule Project.Model do
  use Ecto.Schema
  import Ecto.Changeset

  @type t :: %__MODULE__{}

  @primary_key {:id, Ecto.UUID, autogenerate: false}

  schema "models" do
    field(:name, :string)
    field(:status, Project.Utils.Ecto.AtomType)
    timestamps()
  end

Now, you can set the status value to atom or string, and it will be automagically converted to String when saving to DB. When you read it back, it will set it to atom type.

updated = %{status: :open}

old_schema
|> Model.changeset(updated)
|> Repo.update()

Thanks to Allan for suggesting a solution.

Happy Elixir Coding!