In most cases child field resolvers retrieve data independently. There is a case when this may not be the pattern: an Ecto association that is preloaded. In this case, the child resolver should receive the preloaded data at the first positional argument of a 3 arity resolver function. This can be matched to short cut doing an independent query. In the 1.4 version of Absinthe, this passing of data from parent to child resolver drops fields if the top level is a struct and the child is a struct. The solution seems to be to use Map.from_struct to convert the top level or the map subfield to a regular map. The parent arg will then have the deeper keys.

Concretely, the schema...



object :item do
  field :id, :id
  field :qty, :integer
  field :sku, :id
  field :cart, :cart do
    resolve &cart_resolve/3
  end
end

With a top level resolver with the Ecto query:

def item_resolve( _, %{ id: id }, _ ) do
  from( item in Item,
    where: item.id == ^id,
    join: cart in assoc( item, :cart ),
    preload: [ cart: cart ]
  )
  |> Repo.one
  |> result_tuple
end

From this both the item and the cart will be loaded in one query, no sense in the cart_resolve doing another query. Such as:

def cart_resolve( %{ cart: cart }, _, _ ) do
  { :ok, <do something with the cart data> }
end

The problem is that cart_resolve won't receive the cart from item_resolve. The fix is to change the end of item_resolve to:

...
  |> Repo.one
  |> Map.from_struct
  |> result_tuple

In this case, if cart_resolve doesn't really do anything in the pass through, then the field resolver for :cart can be omitted. If you do that, you still need to convert at least the top resolver result to a regular map otherwise the subfield ( :cart in this case) will be nil.