Absinthe/Ecto changeset with atom enums

Hi,

I have an Absinthe field for the user’s gender that can be either :male or :female.

The problem is that the Ecto schema stores the type as a string, while my Absinthe resolver receives an atom.

Therefore I’m getting an error:
errors: [gender: {"is invalid", [type: :string, validation: :cast]}]

What is the idiomatic way to handle this type mismatch? I’ve tried converting the changeset params with &Atom.to_string/1, but this seems rather hacky:

  defp fix_types(params) do
    params
    |> Map.update(:gender, nil, &Atom.to_string/1)
  end

Is there a better solution?

Thank you!

:wave:

Is using atoms in the ecto schema an option? It can be implemented as a custom ecto type

defmodule YourApp.Gender do
  use Ecto.Type

  genders = [:male, :female]

  def type, do: :string

  for gender <- genders do
    def cast(unquote(gender)), do: {:ok, unquote(gender)}
    def cast(unquote(to_string(gender))), do: {:ok, unquote(gender)}
    
    def load(unquote(to_string(gender))), do: {:ok, unquote(gender)}
    
    def dump(unquote(gender)), do: {:ok, unquote(to_string(gender))}
    def dump(unquote(to_string(gender))), do: {:ok, unquote(to_string(gender))}
  end
  
  def cast(_other), do: :error
  def dump(_other), do: :error
end

or using https://hex.pm/packages/ecto_enum.

3 Likes

Maybe Absinthe Enum Type can help You too…

https://hexdocs.pm/absinthe/Absinthe.Type.Enum.html

Thank you, this seems to be the proper solution!

Thanks, I’m already using it as follows:

  enum :gender do
    value :male
    value :female
  end

The problem is that the Ecto schema expects strings, while the Enum type contains atom values.

I think your problem can be solved by using Absinthe.Middleware.MapGet.

In your schema.ex, define a new apply function which transforms :gender atoms to strings. I assume you have a middleware function like this:

  def middleware(middleware, field, object) do
    middleware
    # |> ...your own apply functions
    |> apply(:get_string, field, object)
  end

  defp apply(middleware, :get_string, field, %{identifier: :gender} = object) do
    new_middleware = {Absinthe.Middleware.MapGet, to_string(field.identifier)}

    middleware
    |> Absinthe.Schema.replace_default(new_middleware, field, object)
  end

I stole this technique almost verbatim from Craft GraphQL APIs in Elixir with Absinthe (Pragprog)

I think enums are coming to Ecto