I'm totally new to Elixir, Phoenix, and Absinthe...so go easy on me. :)
I'm experimenting with a graph database called Dgraph using a library called Dlex. I've written a simple query designed to look up and return a list of users (I only have two users at the moment):
def list_users() do
query = """
{
users(func: type(User)) {
uid
email
name
}
}
"""
{:ok, %{"users" => result}} = Dlex.query(:dlex, query)
IO.inspect(result)
{:ok, result}
end
The output from IO.inspect(result)
is precisely what I expect and want—a list of my two users:
[
%{"email" => "rob@example.com", "name" => "Rob", "uid" => "0x1"},
%{"email" => "bridget@example.com", "name" => "Bridget", "uid" => "0x2"}
]
However, when I run this query with GraphiQL, all the values in the result are null
for some reason:
{
"data": {
"users": [
{
"email": null,
"name": null,
"uid": null
},
{
"email": null,
"name": null,
"uid": null
}
]
}
}
Any idea what I'm doing wrong?
It looks to me like you could be returning the appropriate data in your user resolver, with one exception: when Absinthe goes to resolve the fields for each user, the default resolver only looks for the fields as atom keys. When it can't find :uid
, :email
, or :name
in the parent maps, it returns nil
.
You could convert the result to have atoms as keys. One option if you want to go that route would just be to map over each user and explicitly copy what you want.
users = Enum.map(users, &%{uid: &1["uid"], email: &1["email"], name: &1["name"]})
That is one more place you'd need to update when keys are added, though. And a lot of attempts at more dynamic solutions don't follow best practices (they open the app up to creating new atoms at runtime, which is a bad idea, or throwing errors when they see unrecognized keys).
One solution I've used in the past is creating my own default MapGet middleware that checks for both atom and string keys.
defmodule MyAppWeb.GraphQL.Middleware.MapGet do
@moduledoc """
Default resolver that checks for both atom and string keys.
"""
@behaviour Absinthe.Middleware
@impl Absinthe.Middleware
def call(%{source: source} = info, key) do
value =
with :error <- Map.fetch(source, key),
:error <- Map.fetch(source, to_string(key)) do
nil
else
{:ok, value} ->
value
end
%{info | state: :resolved, value: value}
end
end
The Absinthe docs describe swapping out the default middleware. In my app it was pretty simple.
defmodule MyAppWeb.GraphQL.Schema do
use Absinthe.Schema
# ...
def middleware(middleware, field, object) do
map_get = {{MyAppWeb.GraphQL.Middleware.MapGet, :call}, field.identifier}
Absinthe.Schema.replace_default(middleware, map_get, field, object)
end
# ...
end