Keystone: Relationships missing in virtual resolver callback

Created on 29 Jan 2020  路  6Comments  路  Source: keystonejs/keystone

Bug report

Describe the bug

I'm not able to access fields of relationship type in virtual field resolver method.

To Reproduce

Create model with relationship field of type many and virtual field that should return number of relationships. But object item.answers is undefined.

fields: {
    question: {
      type: Text,
      isRequired: true,
    },
    stats: {
      type: Virtual,
      resolver: (item) => item.answers.length,
    },
    answers: {
      type: Relationship,
      many: true,
      ref: 'PollAnswer',
    },
  },

If I print content of item object I get only:

{ question: 'New on', __v: 0, id: '5e3179fcf456f0b86ae2dab8' }

System information

  • OS: macOS

Most helpful comment

This is actually the expected behaviour, although perhaps it's not well documented. Within a virtual field resolver we don't necessarily know which relationships the developer will need, nor how deeply nested they might need to access things. Also, an item might have millions of related items, and it would be particularly inefficient to immediately look all of these up if they're not actually needed. As it is impossible to know, we resist the temptation to guess and simple provide the item as stored in the database (notice the __v field, which is a mongo thing). If you need to access the related items you will need to perform a subsequent graphql query and define the filters and returned fields on the item.

All 6 comments

This is actually the expected behaviour, although perhaps it's not well documented. Within a virtual field resolver we don't necessarily know which relationships the developer will need, nor how deeply nested they might need to access things. Also, an item might have millions of related items, and it would be particularly inefficient to immediately look all of these up if they're not actually needed. As it is impossible to know, we resist the temptation to guess and simple provide the item as stored in the database (notice the __v field, which is a mongo thing). If you need to access the related items you will need to perform a subsequent graphql query and define the filters and returned fields on the item.

Thanks for this answer Tim, I added this to the virtual field docs: https://github.com/keystonejs/keystone/blob/master/packages/fields/src/types/Virtual/README.md

@timleslie Thanks for the answer! The reason is quite understandable although I think more people will be still confused because the field is completely missing. Why the resolve would not at least return an array of related IDs? Moreover I've got a similar question about missing Virtual types in the resolver method - is it also expected behaviour? If so the Virtual type has no purpose when I can't use the computed value in hooks / access callbacks.

@timleslie When I was changing my code to solve the problem as you've recommended I found out that resolver method should have also access to Query Helper as the hooks so I could easily fetch the data from current context.

Why the resolve would not at least return an array of related IDs?

This operation becomes extremely expensive when an item has hundreds of thousands of related items. We never want to load all of these at once, we always want to apply some user defined filter (unless of course the user has explicitly asked for all hundred-thousand items!).

I think the best way to frame it (and this explains why Virtuals aren't populated either) is that the item you have access to is the item as stored in the database, which is a very different thing to fully resolved item you would see in a graphQL request. Writing custom hooks and resolvers requires this slightly nuanced thinking around the keystone data model, and we should call it out much more explicitly in the docs.

If so the Virtual type has no purpose when I can't use the computed value in hooks / access callbacks.

This is an interesting case indeed. At the moment the solution would be either to explicitly invoke the same code that is invoked for the virtual field resolver, or execute a graphQL query to obtain the virtual fields computed value. Since virtual fields potentially incur significant performance costs (depending on what they're doing) I don't think this is something we'd ever want to evaluate by default, as it could significantly impact the performance of the system. It would potentially lead to circular logic if you have two virtual fields which both require the other to be evaluated.

that resolver method should have also access to Query Helper as the hooks

Yep, I think this is fair, and should be reasonably simple to implement. Could I ask you to create a separate issue for this so that we don't lose track of it. I'm going on vacation for two weeks and if I can't get around to it today I wouldn't want us to lose track of the issue.

I should also mention that the Virtual field is a relatively new feature in Keystone, so I really appreciate that you're putting it through its paces and identifying edge cases that we hadn't considered or accounted for 馃檹

@timleslie Once again, thanks for the exhaustive answer! 馃憤 I'm going to create the issue right now

Was this page helpful?
0 / 5 - 0 ratings

Related issues

molomby picture molomby  路  12Comments

ra-external picture ra-external  路  12Comments

bpavot picture bpavot  路  11Comments

gautamsi picture gautamsi  路  14Comments

molomby picture molomby  路  11Comments