Closed h4rm closed 3 years ago
Hey guys,
just a little update from my testing around. I think the @Relation decorator only works on the top-most level. The workaround I found so war is to have a custom Service that overrides the Mongoose query and model the Relation as a simple @Field
:
Here are the entities:
import { Connection, FilterableField, KeySet, Relation } from '@nestjs-query/query-graphql';
import { ObjectType, Field, InputType } from '@nestjs/graphql';
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { SchemaTypes, Types } from 'mongoose';
import { BaseDTO, BaseEntityMongoose } from '../BaseEntity';
import { Document } from '../document/document.entity';
@Schema({ timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' } })
export class ColorEntity extends Document {
@Prop({ required: true })
name: string;
}
export const ColorSchema = SchemaFactory.createForClass(ColorEntity);
@ObjectType('Color')
@InputType('ColorInput')
@KeySet(['id'])
export class ColorDTO extends BaseDTO {
@Field()
name: string;
}
@Schema({ timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' } })
export class Wall extends Document {
@Prop({ required: true })
name: string;
@Prop({ type: SchemaTypes.ObjectId, ref: 'ColorEntity', required: false })
colorId: Types.ObjectId;
}
export const WallSchema = SchemaFactory.createForClass(Wall);
WallSchema.virtual('color', {
ref: 'ColorEntity', // The model to use
localField: 'colorId', // Find people where `localField`
foreignField: '_id', // is equal to `foreignField`,
justOne: true,
});
@ObjectType('Wall')
@InputType('WallInput')
@KeySet(['id'])
// @Relation('color', () => ColorDTO, {
// disableRemove: true,
// relationName: 'color',
// nullable: true,
// })
export class WallDTO extends BaseDTO {
@Field()
name: string;
@Field({ nullable: true })
colorId?: string;
@Field(() => ColorDTO, { nullable: true })
color?: ColorDTO;
}
@Schema({ timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' } })
export class Room extends Document {
@Prop({ type: [WallSchema], default: [] })
walls: Wall[];
}
export const RoomSchema = SchemaFactory.createForClass(Room);
@ObjectType('Room')
@KeySet(['id'])
export class RoomDTO extends BaseDTO {
@Field(() => [WallDTO])
walls: WallDTO[];
}
Here is the Module:
import { Module } from '@nestjs/common';
import { NestjsQueryGraphQLModule, PagingStrategies } from '@nestjs-query/query-graphql';
import { NestjsQueryMongooseModule } from '@nestjs-query/query-mongoose';
import { ColorDTO, Room, RoomDTO, RoomSchema, Wall, WallDTO, ColorEntity, WallSchema, ColorSchema } from './test.entity';
import { RoomService } from './test.service';
@Module({
imports: [
NestjsQueryGraphQLModule.forFeature({
imports: [
NestjsQueryMongooseModule.forFeature([
{ document: Room, name: Room.name, schema: RoomSchema },
{ document: Wall, name: Wall.name, schema: WallSchema },
{ document: ColorEntity, name: ColorEntity.name, schema: ColorSchema },
]),
],
resolvers: [
{ DTOClass: RoomDTO, EntityClass: Room, ServiceClass: RoomService, pagingStrategy: PagingStrategies.NONE },
{ DTOClass: WallDTO, EntityClass: Wall, pagingStrategy: PagingStrategies.NONE },
{ DTOClass: ColorDTO, EntityClass: ColorEntity, pagingStrategy: PagingStrategies.NONE },
],
services: [RoomService],
}),
],
providers: [],
})
export class RoomModule {}
And here is the Service
import { QueryService } from '@nestjs-query/core';
import { InjectModel } from '@nestjs/mongoose';
import { MongooseQueryService } from '@nestjs-query/query-mongoose';
import { Model, Query } from 'mongoose';
import { Room } from './test.entity';
@QueryService(Room)
export class RoomService extends MongooseQueryService<Room> {
constructor(@InjectModel(Room.name) private model: Model<Room>) {
super(model);
}
async query(query: any): Promise<Room[]> {
const res = await this.model.find({}).populate('walls.color').exec();
return res;
}
}
@h4rm - The auto-populate
plugin might be of interest to you.
https://plugins.mongoosejs.io/plugins/autopopulate
Scott
Edit: Added the plugin to the schema but it's not automatically populating so far.
@Schema({ timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' } })
export class Wall extends Document {
@Prop({ required: true })
name: string;
@Prop({ type: SchemaTypes.ObjectId, ref: 'ColorEntity', required: false })
colorId: Types.ObjectId;
}
export const WallSchema = SchemaFactory.createForClass(Wall);
WallSchema.virtual('color', {
ref: 'ColorEntity', // The model to use
localField: 'colorId', // Find people where `localField`
foreignField: '_id', // is equal to `foreignField`,
justOne: true,
autopopulate: true,
});
// eslint-disable-next-line @typescript-eslint/no-var-requires
WallSchema.plugin(require('mongoose-autopopulate'));
It seems that the autopopulate plugin is not helping here because it also cannot work with nested Schemas as stated here: https://github.com/mongodb-js/mongoose-autopopulate/issues/26
Ah. Bummer that is. Sorry for sending you on a goose chase. @h4rm
Scott
So I think the service approach works best for now and I am happy with the solution.
@h4rm - Can you share some code that shows what you mean?
Scott
Hi @doug-martin , thanks a lot for this amazing library. I was recently looking into the
@Relation
decorator and I would like to use it for querying a reference in a subfield without creating it's own collection (as it is possible with pure mongoose):And the following module:
The main problem is, that the nested relation of
WallDTO
(i.e.color
) is not populated when querying for theRoomDTO
although all GraphQL schemes are correctly generated. I can manually write a service that populates the subfieldswalls.colorId
via mongoose but I initially expected the@Relation
decorator also to work for nested schemas.Maybe I am missing something and it is already working with the current features. I appreciate any feedback. Cheers!
Edit: I know that it would be possible to put
Wall
into its own collection but here I am seeking to explicitly leave it in theRoom
collection as a subfield.