Access to "grand" parent fields in absinthe resolver

Hello everyone.
I have the following query in Absinthe graphql:

doctor {
  doctorId
  patient {
   patientId
   assignments {
     edges {
       node {
         assignmentId
         ...
        }
      }
   }
  }
}

The assignments field should contain all the assignments between the patient and the doctor, and these assignments are obtained by a dedicated resolver, i.e.:

field :assignments,
          :user_assignments,
          meta: [
            trace: true
          ] do
      resolve &AssignmentsResolver.resolve_assignments/3
    end

My problem is that the resolve_assignments function can get the patientId through the parent attribute passed to the resolver, but how can I get the doctorId?

Is there a way to access to “grandparent” data?

Thank you

1 Like

Ok, for anyone who has the same issue, I have found a “workaround” to achieve what I want.
In the particular case I described, i made the method which resolves the patient entity to include, in the map returned to the resolver, a parent_id field which contains the id of the doctor.

In this way, in the resolve_assignments method, in the first (“parent”) attribute I have both the patient_id (parent) and the doctor_id (the “grandparent”).

Furthermore, if the parent_id does not have a corresponding graphql field in the patient entity, it won’t be exposed in the graphql schema but it will be included in the first attribute passed to the resolve_assignments method.

1 Like

@francesco.pessina Hey did you end up keeping that solution or was there something better.

From your experience, would you like to see A feature implemented into absinthe itself, such as A parent object in A resolver function (or a list of ancestors for example)?

@dylan-chong No, I didn’t find anything better. According to my needs, this was a good solution.
Yes, if this feature would be available in absinthe would be great :slight_smile:

You could probably store the ancestors of a field in the Resolution struct using some custom middleware.

The context field of the resolution is persisted between fields being resolved, so can be used to accumulate the ancestors.

1 Like

Interesting idea @maartenvanvliet! I’m not sure if that will work, as context is a global field and would be tricky to assemble with a simple and a tree structure like this

a
  b
    c
  b
    c
  b
    c

|> Get grandparent value in resolver function 😭 · Issue #1132 · absinthe-graphql/absinthe · GitHub

It’s conceivable to do this in Absinthe, although one issue is that it adds a performance penalty for every field traversal even if it is never used, since Absinthe has to do book keeping to track the object stack, and to persist it in the case of suspended resolution.

In general, this is a bit of a fragile feature. If you add a new field to the graph and return that object, the resolver which depends on the grandparent will have a new kind of grand parent potentially.

A better solution IMHO is to use Dataloader to explicitly try to load the value you are asking for. Then, in certain fields if you expect to always need a value, you could explicitly set the value in dataloader so that it is basically pre-cached.

2 Likes

It’s conceivable to do this in Absinthe, although one issue is that it adds a performance penalty for every field traversal even if it is never used, since Absinthe has to do book keeping to track the object stack, and to persist it in the case of suspended resolution.

A better solution IMHO is to use Dataloader to explicitly try to load the value you are asking for. Then, in certain fields if you expect to always need a value, you could explicitly set the value in dataloader so that it is basically pre-cached.

I wonder if this would be tricky in the generic case because you dont know what the root is. E.g. if c needs to access a, with a->b->c, but you could have d->a->b->c, or e->d->a->b->c

i have a temporary solution at the moment which is to store, on b, a reference to a.field by adding a virtual b.parent_field to it’s Ecto Schema, then manually populating it in all of a’s resolver functions

I’m not clear how this is an issue for my implementation, but it is definitely an issue for your proposed one. If you do:

value = parent.grand_parent

What is the type of grand_parent? You have no way of knowing. Instead of relying on what happens to be the GraphQL ancestor you should explicitly try to load some value for a given parent object.

hmm i’ll have a play with dataloader at some point to get a better understanding of the library.

I’m not clear how this is an issue for my implementation, but it is definitely an issue for your proposed one. If you do:

if you are at c and you want to get the grandparent, you would look up the 2nd ancestor. this would always work as long as b can only be a child of a (in our case this is true)

Right, but to be part of Absinthe it has to work in the general case. In the general case, b can be the child of basically any object. If, in your case, b can only be a child of a via a specific field then the “pass the GP down as a value to the parent” pattern works great and is definitely the route I would recommend.

1 Like