I couldn't find a solid answer to this either so I threw this together. I would love a more "official" answer. Maybe like a sweet @View
decorator or something?
// feature.module.ts
@Module({
imports: [
// views
MongooseModule.forFeature([{ name: LogHydrated.name, schema: LogHydratedSchema }]),
// collections
MongooseModule.forFeature([{ name: Log.name, schema: LogSchema }]), ],
providers: [...]
})
export class FeatureModule {
@InjectConnection() private readonly connection: Connection
async onModuleInit (): Promise<void> {
const collections = await this.connection.db.listCollections().toArray()
if (collections.every(collection => collection.name !== 'logs_hydrated')) {
await this.connection.db.createCollection('logs_hydrated', {
viewOn: 'logs',
pipeline: [/*aggregation pipeline here */]
})
}
}
}
// log.schema.ts
@Schema({ collection: 'logs' })
export class Log extends Mongo implements MongoDoc {
//...some props
}
export const LogSchema = SchemaFactory.createForClass(Log)
export type LogDocument = Log & Document
// autoCreate: false is what makes this work. The module creates the "view" collection
// on init and the model relies on the view collection always being present
@Schema({ collection: 'logs_hydrated', autoCreate: false })
export class LogHydrated extends Log {
@Prop({ type: [LogField] })
fields: LogField[]
}
export const LogHydratedSchema = SchemaFactory.createForClass(LogHydrated)
export type LogHydratedDocument = LogHydrated & Document
// feature.service.ts
export class LogService {
constructor (
@InjectModel(Log.name) private readonly logModel: Model<LogDocument>,
@InjectModel(LogHydrated.name) private readonly logHydratedModel: Model<LogHydratedDocument>
) {}
async findById (id: string): Promise<LogHydrated> {
try {
const model = await this.logHydratedModel.findById(id)
if (model === null) {
throw new HttpException(`Unable to find log by id: ${String(id)}`, HttpStatus.BAD_REQUEST)
}
return model
} catch (error) {
// handle error
}
}
}
Edit: I discovered a method to do this a bit less hacky. It doesn't actually create a view but it lets you populate virtual properties in which you can achieve the same effect.
// log.schema.ts
// notice the @Type decorator vs @Prop
class Log {
...
@Type(() => LogField)
fields: LogField[]
}
export const LogSchema = SchemaFactory.createForClass(Log)
LogSchema.virtual('fields', {
ref: 'LogField',
localField: 'fieldIds',
foreignField: '_id'
})
// feature.service.ts
// call populate
const model = await this.LogModel.findById(id).populate({
path: 'fields'
})
this one prevents the need for multiple models