8

How do I represent a field that could be either a simple ObjectId string or a populated Object Entity?

I have a Mongoose Schema that represents a 'Device type' as follows

// assetSchema.js

import * as mongoose from 'mongoose'
const Schema = mongoose.Schema;

var Asset = new Schema({  name : String,
                          linked_device: { type: Schema.Types.ObjectId, 
                                           ref: 'Asset'})

export AssetSchema = mongoose.model('Asset', Asset);

I am trying to model this as a GraphQLObjectType but I am stumped on how to allow the linked_ue field take on two types of values, one being an ObjectId and the other being a full Asset Object (when it is populated)

// graphql-asset-type.js

import { GraphQLObjectType, GraphQLString } from 'graphql'

export var GQAssetType = new GraphQLObjectType({
           name: 'Asset',
           fields: () => ({
               name: GraphQLString,
               linked_device: ____________    // stumped by this
});

I have looked into Union Types but the issue is that a Union Type expects fields to be stipulated as part of its definition, whereas in the case of the above, there are no fields beneath the linked_device field when linked_device corresponds to a simple ObjectId.

Any ideas?

viztastic
  • 1,815
  • 1
  • 17
  • 17

2 Answers2

9

As a matter of fact, you can use union or interface type for linked_device field.

Using union type, you can implement GQAssetType as follows:

// graphql-asset-type.js

import { GraphQLObjectType, GraphQLString, GraphQLUnionType } from 'graphql'

var LinkedDeviceType = new GraphQLUnionType({
  name: 'Linked Device',
  types: [ ObjectIdType, GQAssetType ],
  resolveType(value) {
    if (value instanceof ObjectId) {
      return ObjectIdType;
    }
    if (value instanceof Asset) {
      return GQAssetType;
    }
  }
});

export var GQAssetType = new GraphQLObjectType({
  name: 'Asset',
  fields: () => ({
    name: { type: GraphQLString },
    linked_device: { type: LinkedDeviceType },
  })
});

Check out this excellent article on GraphQL union and interface.

Ahmad Ferdous
  • 3,351
  • 16
  • 33
  • Thanks for the help @AhmadFerdous . A question, in the line `value instanceof Objectid`, what is `ObjectId`? Is it `var ObjectId = mongoose.Schema.Types.ObjectId` ? Also how what does `ObjectIdType` look like? – viztastic Jan 02 '17 at 10:36
  • 1
    Yes, it's mongoose ObjectId. `ObjectIdType` is just a wrapper GraphQL object type, which can have a string field for the id. – Ahmad Ferdous Jan 02 '17 at 12:17
  • Thanks @AhmadFerdous this is really helpful. I have also asked another question here that you might be able to help with, really appreciate the support: http://stackoverflow.com/questions/41427320/how-to-handle-hyphens-in-graphql-schema-definitions – viztastic Jan 02 '17 at 13:00
  • not sure where ObjectIdType come from.. its new ObjectId? can you provide full working example please? – Shlomi Levi Mar 24 '19 at 06:42
6

I was trying to solve the general problem of pulling relational data when I came across this article. To be clear, the original question appears to be how to dynamically resolve data when the field may contain either the ObjectId or the Object, however I don't believe it's good design in the first place to have a field store either object or objectId. Accordingly, I was interested in solving the simplified scenario where I keep the fields separated -- one for the Id, and the other for the object. I also, thought employing Unions was overly complex unless you actually have another scenario like those described in the docs referenced above. I figured the solution below may interest others also...

Note: I'm using graphql-tools so my types are written schema language syntax. So, if you have a User Type that has fields like this:

type User {
    _id: ID
    firstName: String
    lastName: String
    companyId: ID
    company: Company
}

Then in my user resolver functions code, I add this:

  User: {   // <-- this refers to the User Type in Graphql
    company(u) {   // <-- this refers to the company field
      return User.findOne({ _id: u.companyId }); // <-- mongoose User type
    },
  }

The above works alongside the User resolver functions already in place, and allow you write GQL queries like this:

query getUserById($_id:ID!) 
    { getUserById(_id:$_id) {
    _id
    firstName
    lastName
    company {
        name
    }
    companyId
    }}

Regards,

S. Arora

sarora
  • 913
  • 9
  • 20
  • This does not look like an answer to this question. If you have an answer to a different question, try and find a question that actually asks the question you want to answer and post your answer there. If no such question exists, you can post one yourself if you think it's a useful one. Note that all answers must be attempts to answer the question on top, not just some related question. – Baum mit Augen Sep 29 '17 at 16:04
  • Its an attempt to answer what is likely the underlying challenge...the question was asked and answered as asked..but i'm trying to reframe what the right question to ask is, and questioning the premise. It's not good design to have a field store either object or objectId, so i'm suggesting there is a better approach with a simpler answer. Just a thought. – sarora Sep 29 '17 at 16:56
  • Ah ok. You may want to make that more clear in the answer itself. – Baum mit Augen Sep 29 '17 at 17:00
  • clarified. thanks for the feedback. upvote appreciated! – sarora Sep 29 '17 at 17:09
  • Thanks for the clarification. Unfortunately, I'm not fit to vote on this post as I have no domain knowledge and thus cannot really judge the content. – Baum mit Augen Sep 29 '17 at 17:13