graphql-compose / graphql-compose-mongoose

Mongoose model converter to GraphQL types with resolvers for graphql-compose https://github.com/nodkz/graphql-compose
MIT License
708 stars 94 forks source link

"Discriminator Key not Set" error with descriminatorKey set in the schema #357

Closed brunomptavares closed 2 years ago

brunomptavares commented 3 years ago

Hello, I found this issue while trying to create a DiscriminatorTypeComposer from a Mongoose Model.

//Create user model
const userSchema = new Mongoose.Schema({
  name: { type: String, unique: true, required: true },
  email: { type: String, unique: true, required: true },
  type: { type: String, required: true, enum: Object.keys(enumUserType) }
}, { timestamps: true });

//Set discriminator key
userSchema.set('discriminatorKey', 'type');

//Create person mongoose Model
const User = Mongoose.model('User', userSchema);
//Create person DiscriminatorTypeComposer
const { composeWithMongooseDiscriminators } = require('graphql-compose-mongoose');
const UserDTC = composeWithMongooseDiscriminators(User);

The problem seems to be in the DiscriminatorTypeComposer.js file, in the following section:

...
static createFromModel(baseModel, schemaComposer, opts) {
  if (!baseModel || !baseModel.discriminators) {
      throw Error('Discriminator Key not Set, Use composeWithMongoose for Normal Collections');
  }
...

When debugging my code I inspected the User model and the User.discriminators field is indeed undefined, although the discriminatorKey property is set inside userSchema.options.

I'm not sure if this is some leftover property from an older Mongoose version but it seems like it's doing nothing in there.

After a lot of digging I added User.discriminators = enumPersonType to my code and I don't get the error anymore.

nodkz commented 3 years ago

Did you try mongoose descriminator function ("Mongoose v5.13.2: Discriminators" https://mongoosejs.com/docs/discriminators.html) ?

can you provide full example of your code with several inherited models?

brunomptavares commented 3 years ago

Sure, below there is a code snippet where I create the Student model using the User.discriminator function that I think you are refering to.

//Define mongoose Schema
const studentSchema = new Mongoose.Schema({
  class: { type: String, enum: ['FIRST_YEAR', 'SECOND_YEAR', 'IMERSION_WEEK', 'INTERNSHIP'], required: true}
}, { timestamps: true })

//Create mongoose Model
const { User, UserDTC, enumUserType, userSchema } = require('./User')
const Student = User.discriminator(enumUserType.STUDENT, studentSchema);

//GraphQL TypeComposer from User DiscriminatorTypeComposer
const StudentTC = UserDTC.discriminator(Student);
jmtt89 commented 2 years ago

in my case the problem is the order... is needed set models (all) before types like the example in Readme

brunomptavares commented 2 years ago

in my case the problem is the order... is needed set models (all) before types like the example in Readme

I'm not sure if I got your comment right, but here is the order of imports in the code:

const User = require('../models/user/User');
const Admin = require('../models/user/Admin');
const Student = require('../models/user/Student');
const Teacher = require('../models/user/Teacher');

Also fixed a typo in the code from the first comment. Where it was Person it should read User. This is the base model from which Admin, Student and Teacher models are extended.

brunomptavares commented 2 years ago

in my case the problem is the order... is needed set models (all) before types like the example in Readme

Thanks to you I finally found the issue. I really have to "respect" some kind of order:

  1. Create User (base) model
  2. Create Student, Teacher and Admin (child) models
  3. Create User DTC
  4. Create Student, Teacher and Admin TCs

I looked up into this example.

Unfortunately I had to move all the code to one single file, when before I had it in separated files.

jmtt89 commented 2 years ago

Not is necessary move all to same file, my project structure is:

| src
| server.js
|  | schema
|  |  | index.js
|  |  | parent.js
|  |  | childA.js
|  |  | childB.js
|  | models
|  |  | index.js
|  |  | parent
|  |  |  |  model.js
|  |  |  |  types.js
|  |  |  |  index.js
|  |  | childA
|  |  |  |  model.js
|  |  |  |  types.js
|  |  |  |  index.js
|  |  | childB
|  |  |  |  model.js
|  |  |  |  types.js
|  |  |  |  index.js

in model files export only mongoose implementation, yo know the official doc.

Parent Model models/parent/model.js

const schema= new mongoose.Schema({ ... }, options);
module.exports = mongoose.model('Parent', schema);

Child Model models/childA/model.js

const Parent = require('../parent/model.js')
...
module.exports = Parent.discriminator('TYPE_A', new mongoose.Schema({ ... }, options));

in types file export the TypeComposer explained in the Readme

models/parent/type.js

const Parent = require(./model.js)
...
module.exports = composeWithMongooseDiscriminators(Parent, tcOptions)

models/childA/type.js

const ParentType = require('../parent/types')
const Child = require(./model.js)
...
module.exports = ParentType.discriminator(Child, tcOptions)

in schema folder create one file per child with Query and Mutatios

schema/childA.js

const Type = require('../models/childA/types')
module.exports = {
   Query: {
      childACreateOne: Type.getResolver('createOne'),
      ....
   },
   Mutation: {
      ....
   }
}

schema/childB.js

const Type = require('../models/childB/types')
module.exports = {
   Query: {
      childBCreateOne: Type.getResolver('createOne'),
      ....
   },
   Mutation: {
      ....
   }
}

And generate the main schema on index.js

schema/index.js

const { schemaComposer } = require('graphql-compose')

const ChildA = require('./childA')
const ChildB = require('./childB')

schemaComposer.Query.addFields({
    ...ChildA.Query,
    ...ChildB.Query,
})

schemaComposer.Mutation.addFields({
    ...ChildA.Mutation,
    ...ChildB.Mutation,
})

module.exports = schemaComposer.buildSchema()
jmtt89 commented 2 years ago

a important note: discriminator not are rewrite to new library logic, and not have any plans to do it --> https://github.com/graphql-compose/graphql-compose-mongoose/issues/281

because of that use function composeWithMongooseDiscriminators instead composeMongooseDiscriminators

brunomptavares commented 2 years ago

Not is necessary move all to same file, my project structure is:

| src
| server.js
|  | schema
|  |  | index.js
|  |  | parent.js
|  |  | childA.js
|  |  | childB.js
|  | models
|  |  | index.js
|  |  | parent
|  |  |  |  model.js
|  |  |  |  types.js
|  |  |  |  index.js
|  |  | childA
|  |  |  |  model.js
|  |  |  |  types.js
|  |  |  |  index.js
|  |  | childB
|  |  |  |  model.js
|  |  |  |  types.js
|  |  |  |  index.js

in model files export only mongoose implementation, yo know the official doc.

Parent Model models/parent/model.js

const schema= new mongoose.Schema({ ... }, options);
module.exports = mongoose.model('Parent', schema);

Child Model models/childA/model.js

const Parent = require('../parent/model.js')
...
module.exports = Parent.discriminator('TYPE_A', new mongoose.Schema({ ... }, options));

in types file export the TypeComposer explained in the Readme

models/parent/type.js

const Parent = require(./model.js)
...
module.exports = composeWithMongooseDiscriminators(Parent, tcOptions)

models/childA/type.js

const ParentType = require('../parent/types')
const Child = require(./model.js)
...
module.exports = ParentType.discriminator(Child, tcOptions)

in schema folder create one file per child with Query and Mutatios

schema/childA.js

const Type = require('../models/childA/types')
module.exports = {
   Query: {
      childACreateOne: Type.getResolver('createOne'),
      ....
   },
   Mutation: {
      ....
   }
}

schema/childB.js

const Type = require('../models/childB/types')
module.exports = {
   Query: {
      childBCreateOne: Type.getResolver('createOne'),
      ....
   },
   Mutation: {
      ....
   }
}

And generate the main schema on index.js

schema/index.js

const { schemaComposer } = require('graphql-compose')

const ChildA = require('./childA')
const ChildB = require('./childB')

schemaComposer.Query.addFields({
    ...ChildA.Query,
    ...ChildB.Query,
})

schemaComposer.Mutation.addFields({
    ...ChildA.Mutation,
    ...ChildB.Mutation,
})

module.exports = schemaComposer.buildSchema()

Thank's I really appreciate it! I had the code for the type and models inside the same file, like this makes much more sense.