(Protocol.UndefinedError) protocol Enumerable not implemented for #Ecto.Changeset

In my GraphQL API there is a schema.ex file with mutation which creates a new user:

    @desc "Register a new user"
    field :register_user, type: :user_type do
      arg(:input, non_null(:user_input_type))
      resolve(&Resolvers.UserResolver.register_user/3)
    end

user_resolver.ex

  def register_user(_, %{input: input}, _) do
    Accounts.create_user(input)
  end

user.ex

def changeset(user, attrs) do
user
    |> cast(attrs, [:first_name, :last_name, :login, :email, :password, :password_confirmation, :role])
    |> validate_required([:first_name, :last_name, :login, :email, :password, :password_confirmation, :role])
    |> validate_format(:email, ~r/@/)
    |> update_change(:email, &String.downcase(&1))
    |> validate_length(:password, min: 6, max: 50)
    |> validate_confirmation(:password)
    |> unique_constraint(:email)
    |> hash_password
end

If I create a new user and all the conditions from the changeset will be fulfilled everything is ok. But if I for instance put an e-mail which is already in the DB (it should be unique) I get an error:

mutation {
  registerUser(input: {firstName: "Jan", lastName: "Nowak", login: "jnowak", email: "bob1@bob.com", password: "test123
", passwordConfirmation: "test123"}) {
firstName
  }
}
---
[debug] QUERY ERROR db=9.0ms
INSERT INTO "users" ("email","first_name","last_name","login","password_hash","role","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING "id" ["bob1@bob.com", "Jan", "Nowak", "jnowak", "$argon2i$v=19$m=65536,t=6,p=1$uAEr21JgCiN2Vq8g3c7abA$u/cyXghpQvq4hg8VoszWkIREErb7qDSlNm+1J6PiSGg", "user", {{2018, 11, 15}, {18, 32, 54, 147581}}, {{2018, 11, 15}, {18, 32, 54, 150554}}]
[info] Sent 500 in 1200ms
[error] #PID<0.411.0> running MdbmsBackendWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:4000 (http)
Request: POST /api/graphiql
** (exit) an exception was raised:
    ** (Protocol.UndefinedError) protocol Enumerable not implemented for #Ecto.Changeset<action: :insert, changes: %{email: "bob1@bob.com", first_name: "Jan", last_name: "Nowak", login: "jnowak", password_confirmation: "test123", password_hash: "$argon2i$v=19$m=65536,t=6,p=1$uAEr21JgCiN2Vq8g3c7abA$u/cyXghpQvq4hg8VoszWkIREErb7qDSlNm+1J6PiSGg", role: "user"}, errors: [email: {"has already been taken", []}], data: #MdbmsBackend.Accounts.User<>, valid?: false>. This protocol is implemented for: DBConnection.PrepareStream, DBConnection.Stream, Date.Range, Ecto.Adapters.SQL.Stream, File.Stream, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, List, Map, MapSet, Postgrex.Stream, Range, Stream
        (elixir) /private/tmp/elixir-20180620-66895-1xvgf6i/elixir-1.6.6/lib/elixir/lib/enum.ex:1: Enumerable.impl_for!/1
        (elixir) /private/tmp/elixir-20180620-66895-1xvgf6i/elixir-1.6.6/lib/elixir/lib/enum.ex:141: Enumerable.reduce/3
        (elixir) lib/enum.ex:1911: Enum.reverse/1
        (elixir) lib/enum.ex:2588: Enum.to_list/1
        (absinthe) lib/absinthe/phase/document/execution/resolution.ex:351: Absinthe.Phase.Document.Execution.Resolution.split_error_value/1
        (absinthe) lib/absinthe/phase/document/execution/resolution.ex:341: Absinthe.Phase.Document.Execution.Resolution.put_result_error_value/5
        (elixir) lib/enum.ex:1899: Enum."-reduce/3-lists^foldl/2-0-"/3
        (absinthe) lib/absinthe/phase/document/execution/resolution.ex:256: Absinthe.Phase.Document.Execution.Resolution.build_result/4
        (absinthe) lib/absinthe/phase/document/execution/resolution.ex:153: Absinthe.Phase.Document.Execution.Resolution.do_resolve_fields/6
        (absinthe) lib/absinthe/phase/document/execution/resolution.ex:72: Absinthe.Phase.Document.Execution.Resolution.walk_result/5
        (absinthe) lib/absinthe/phase/document/execution/resolution.ex:53: Absinthe.Phase.Document.Execution.Resolution.perform_resolution/3
        (absinthe) lib/absinthe/phase/document/execution/resolution.ex:24: Absinthe.Phase.Document.Execution.Resolution.resolve_current/3
        (absinthe) lib/absinthe/pipeline.ex:274: Absinthe.Pipeline.run_phase/3
        (absinthe_plug) lib/absinthe/plug.ex:421: Absinthe.Plug.run_query/4
        (absinthe_plug) lib/absinthe/plug.ex:247: Absinthe.Plug.call/2
        (phoenix) lib/phoenix/router/route.ex:147: Phoenix.Router.Route.forward/4
        (phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
        (mdbms_backend) lib/mdbms_backend_web/endpoint.ex:1: MdbmsBackendWeb.Endpoint.plug_builder_call/2
        (mdbms_backend) lib/plug/debugger.ex:122: MdbmsBackendWeb.Endpoint."call (overridable 3)"/2
        (mdbms_backend) lib/mdbms_backend_web/endpoint.ex:1: MdbmsBackendWeb.Endpoint.call/2

I wonder what is the reason of this UnderfinedError. It causes also a problem when I use this API in my frontend. Console.log returns “POST 500 (Internal Server Error)”, but only when I do not fullfil conditions from the changeset.

I hope this information will help you understand my issue.

Seems like absinthe can’t work with ecto changesets out of the box. AFAIK you might need to define either some kind of middleware to handle the errors (changesets) or build “error” responses manually depending on the result of Accounts.create_user(input).

1 Like

You could also look into https://github.com/Ethelo/kronky to convert invalid changesets to GraphQL errors.

1 Like