Typegoose - ObjectId that references a Sub-Document - mongodb

I'm trying to pull a ref from a Subdocument. The Documents looks like this.
Sample Categories Doc:
{
"_id": 12345
"keyword": "smart phones",
"subCategories": [
{
"_id": 6789,
"keyword": "apple"
},
{
"_id": 101123,
"keyword": "samsung"
}
]
}
Sample Dictionary Doc:
{
"_id": 12345
"keyword": "iPhone",
"category": 12345,
"subCategory": 6789
}
Here's what I've tried to do on Typegoose model definition:
For Dictionary (notice the subCategory prop, I'm not sure if that's the right way to reference a subdocument):
export class Dictionary extends Typegoose {
_id!: Schema.Types.ObjectId;
#prop({
default: 1
})
type!: IDictionaryInput['type'];
#prop()
mainKeyword!: string;
#arrayProp({ items: Synonym })
synonyms!: Synonym[];
#prop({ ref: Category })
category!: Ref<Category>;
#prop({ ref: CategoryModel.subCategories })
subCategory!: Ref<Subcategory>;
#prop({
default: true
})
status!: IDictionaryInput['status'];
}
For Categories:
export class Category extends Typegoose {
_id!: Schema.Types.ObjectId;
#prop()
keyword!: ICategoryInput['keyword'];
#prop({
default: 1
})
type!: ICategoryInput['type'];
#arrayProp({ items: Subcategory })
subCategories!: Subcategory[];
#prop({
default: true
})
status!: ICategoryInput['status'];
#prop()
insertTimestamp!: ICategoryInput['insertTimestamp'];
}
Then I tried to populate the references by doing:
DictionaryModel.findOne({ _id: id })
.populate({
path: 'category',
model: CategoryModel
})
.populate({
path: 'subCategory',
model: CategoryModel.subCategories
});
I can successfully populate the ref from category but not on subCategory.

Related

Enable to set ObjectId in nested array in nest.js

I have a module of question, and I want a structure something like
[ { question: 'REFERENCE ID', answer: { start_value: '1', end_value: '2' } } ],
when I try to save it, it save question id as string but I want to store questionId as ObjectId so I can populate it in other request.
while and the complete response I want is:
{
"answer_by": ObjectId(''), // string, should be user reference ID for populating user
"answers": [
{
"question": ObjectId(''), // string, should be user reference ID for populating question
"answer": {
"start_value": "val1",
"end_value": "val2"
}
}
]
}
here is my schema respectively!
import * as mongoose from "mongoose";
import { Person } from "./person.schema";
import { PostMeetingChecklist, PostMeetingChecklistDocument } from "./post-meeting-checklist.schema";
import { Model, Schema as MongooseSchema } from "mongoose";
import { Resource } from "./resource.schema";
#Schema()
export class Answer {
#Prop()
start_value: string;
#Prop()
end_value: string;
}
#Schema()
export class Item {
#Prop({ type: mongoose.Schema.Types.ObjectId, ref: Question.name})
question: string;
#Prop()
answer: Answer;
}
export type QuestionDocument = Question & mongoose.Document;
#Schema({ timestamps: true })
export class Question {
#Prop({ type: mongoose.Schema.Types.ObjectId, ref: User.name })
answer_by: string;
#Prop()
answers: [Item];
}
export const QuestionSchema = SchemaFactory.createForClass(Question);
in mongoDb,it takes answer_by as ObjectId but question as string, how to fixed that?
Further, I also want to populate this like:
{
"answer_by": User Details,
"answers": [
{
"question": Question Details,
"answer": {
"start_value": "5",
"end_value": "10"
}
}
],
"_id": "63f3146f9f84a58be0b32ad8",
"createdAt": "2023-02-20T06:34:23.300Z",
"updatedAt": "2023-02-20T06:34:23.300Z",
"__v": 0
}
Have tried
#Prop()
answers: [{
question:{
type:mongoose.Schema.Types.ObjectId,
ref:"PostMeetingChecklist"
},
answer: Answer
}];
but in vain.

trying to populate a single field in each object inside an array of objects by dynamically picking the model to use with mongoose

I'm trying to use refPath to reference which collection to pull the population data from inside my schema, and even though it looks identical to the examples I've seen, its just not working.
Here is my schema for statesPersons, not super important, but it contains the activeWork array of objects.
import mongoose, {model, Schema} from "mongoose";
const statesPersonsSchema = new Schema(
{
profileId: {
type: String,
required: true,
unique: true,
},
department: {
type: String,
required: true,
index: true,
},
firstName: String,
lastName: String,
location: String,
org: String,
title: String,
jobDescription: String,
email: {
type: String,
lowercase: true,
},
phoneNumber: String,
activeWork: ["activeWork"],
emailList: [String],
jobAssignments: [String],
affiantInfo: {
affiantInfoTitle: String,
affiantInfoExperience: String,
},
assessments: [
{
assessdBy: {
type: Schema.Types.ObjectId,
ref: "statesPerson",
},
dueDate: Date,
questions: {},
},
],
},
{ strictPopulate: false }
);
export default mongoose.model("statesPersons", statesPersonsSchema);
Here is my schema for activeWork, the array of objects. This has the referenceId that I need to populate as well as the collectionType which I pull what collection it is from.
import mongoose, {model, Schema} from "mongoose";
const activeWorkSchema = new Schema(
{
active: Boolean,
collectionType: {
type: String,
enum: ["messages", "cases"],
},
referenceId: {
type: Schema.Types.ObjectId,
refPath: "collectionType",
},
sentBy: {
type: Schema.Types.String,
ref: "statesPersons",
},
sentTo: {
type: Schema.Types.String,
ref: "statesPersons",
},
timeRecived: Date,
dueDate: Date,
subject: String,
viewed: Boolean,
content: {},
},
{ strictPopulate: false }
);
export default mongoose.model("activeWork", activeWorkSchema);
And here is my query.
export async function getStatesPersonsActiveWorkByProfileId(req, res){
mongoose.set('debug', true);
try{
const { profileId } = req.params
const data = await statesPersons
.find({ profileId })
.populate('statesPersons.activeWork.referenceId')
.exec()
return res.send({
message: "success",
data: data,
status: 200 })
}catch(e) {
console.error(e.message)
return res.send({
message: "couldn't fetch active work",
data: null,
status: 500 })
}
}
its returning with the statesPersons object and the activeWork contains the objectId I need to populate, but its not populating. it looks like this.
"activeWork": [
{
"active": true,
"collectionType": "messages",
"referenceId": "63a49e3052658ce60c1dafcb",
"sentBy": "108416469928574003772",
"dueDate": "2018-02-21T11:16:50.362Z",
"subject": "testing",
"viewed": false,
"_id": "63a49e3052658ce60c1dafce"
I can force it to work by changing the query to be explicit.
const data = await statesPersons
.find({ profileId })
.populate({path: 'activeWork.referenceId', model: 'messages'})
.exec()
which looks like this.
activeWork": [
{
"active": true,
"collectionType": "messages",
"referenceId": {
"_id": "63a49e3052658ce60c1dafcb",
"involvedParties": [
"108416469928574003772",
"100335565301468600000"
],
"comments": [
{
"sender": [
"108416469928574003772"
],
"dateSent": "2022-12-22T18:13:04.604Z",
"content": "There is no way this is going to work.",
"_id": "63a49e3052658ce60c1dafcc"
}
],
But this wont work because I need it to be able to pull what model to use from the collectionType field
sorry for the late response , it seems like you are trying to populate the multilevel documents multilevel population.
here is an example.
db.statesPersonsSchema.find({ profileId }). populate({
path: 'activeWorkSchema',
populate: { path: 'referenceId' }
});

Add a new field to the Typegoose subdocument

How do I include an extra field in the User class subdocument?
class Car {
#prop()
public model?: string;
}
class User {
#prop()
public name?: string;
#prop({ required: true })
public age!: number;
#prop({ ref: () => Car })
public cars?: Ref<Car>[];
}
Populated Car Collection:
Car A
{
"_id": "1"
"model": "Ferrari"
}
Car B
{
"_id": "2"
"model": "Tesla"
}
User collection populated according to the default class:
User
{
"_id": "1",
"name": "Jonh Doe",
"age": 25,
"cars": [
{ "_id": "1" },
{ "_id": "2" }
]
}
I need to include a "status" field in the cars array of the User collection, as shown below:
status is to identify whether the car model is active for the user
User
{
"_id": "1",
"name": "Jonh Doe",
"age": 25,
"cars": [
{ "_id": "1", "status": false },
{ "_id": "2", "status": true }
]
}
Hasezoey helped me with the github issue.
Here's the link and his answer.
https://github.com/typegoose/typegoose/discussions/726
You have multiple options:
make the current reference array a subdocumentarray (see example 1)
include the status property on the car itself
have a separate class that has a unique compound index on the user-car references and has the properties you want and have such a reference field in the user (either through virtual populate or with a explicit ref array) (see example 2)
Example 1:
class Car {
#prop()
public model?: string;
}
class UserCarSub {
#prop({ ref: () => Car })
public car: Ref<Car>;
#prop()
public extra_properties: string;
}
class User {
#prop()
public name?: string;
#prop({ required: true })
public age!: number;
#prop()
public cars?: UserCarSub[];
}
Example 2:
// the first example does not use virtual populate
class Car {
#prop()
public model?: string;
}
class UserCarProperties {
#prop({ ref: () => Car })
public car: Ref<Car>;
#prop({ ref: () => User })
public user: Ref<User>;
#prop()
public extra_properties: string;
}
class User {
#prop()
public name?: string;
#prop({ required: true })
public age!: number;
#prop({ ref: () => Car })
public cars?: Ref<Car>[];
#prop({ ref: () => UserCarProperties })
public car_properties: Ref<UserCarProperties>[];
}
// the following uses virtual populate
class User {
#prop()
public name?: string;
#prop({ required: true })
public age!: number;
#prop({ ref: () => Car })
public cars?: Ref<Car>[];
#prop({ ref: () => UserCarProperties, foreignField: 'user', localField: '_id' })
public car_properties: Ref<UserCarProperties>[]; // this property does not exist in the database, but can still populate
}
Virtual Populate
Alternatively you could also in Example 2 merge the cars references into the UserCarProperties and use that to find cars, that is fully up to how you want it
Personally i would suggest Example 2 with virtual populate (merged) if the array is of unknown length or could grow to unknown length

Mongoose Populate() replaces array with object

My schema looks like this
Schema()
export class User {
#Prop({ required: true })
name: string;
#Prop({ required: true })
email: string;
#Prop({ type: mongoose.Types.ObjectId, ref: 'Course' })
courses: Course[];
}
#Schema()
export class Course {
#Prop()
name: string;
#Prop([{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }])
participants: User[];
#Prop([{ type: mongoose.Schema.Types.ObjectId, ref: 'Lesson' }])
lessons: Lesson[];
}
what i'm trying to achieve is to get an array of courses that a specific user is signed up for. The way Im trying to do this looks like this.
async findUserCoursesByUserEmail(email) {
const user = await this.userModel
.findOne({ email })
.populate('courses')
.exec();
return user.courses;
}
But in response i get an object like this, but it should be an array.
{
"lessons": [],
"participants": [
"6157a5ba06afba420464fb65"
],
"_id": "6157a5ac06afba420464fb61",
"name": "Matematyka Rozszrzona",
"__v": 1
}
The user object looks like this.
{
"_id": "6157a5ba06afba420464fb65",
"name": "Jacob",
"email": "test#test.pl",
"courses": [
"6157a5ac06afba420464fb61",
"6158d64891be3f50f85bef0a"
],
"__v": 2
}
Any help would be highly appreciated cause i'm stuck for quite some time with this one.
as per nestjs/mongodb docs change the:
#Prop({ type: mongoose.Types.ObjectId, ref: 'Course' })
courses: Course[];
on your UserSchema to:
#Prop({ type: [{mongoose.Types.ObjectId, ref: 'Course'}] })
courses: Course[];

Is it possible to have in Mongodb (with mongoose and typegoose) an index on array of nested keys?

In mongodb (using mongoose and typegoose) is it possible to have an array index on a nested key?
export class Member extends Typegoose {
#prop({ required: true })
public email!: string;
#prop({ required: true })
private userId!: string
}
#index({ 'members.userId': 1 })
export class Group {
#arrayProp({ items: Member })
public members: Member[];
#prop()
name: string;
}
If so, how can I query this collection if I'd want to find a group by userId?
Like this?
Group.findOne({ usersIds: userId })
yes it is possible, here is how you can do it:
// NodeJS: 14.5.0
// MongoDB: 4.2-bionic (Docker)
import { getModelForClass, prop, index } from "#typegoose/typegoose"; // #typegoose/typegoose#7.3.0
import * as mongoose from "mongoose"; // mongoose#5.9.25 #types/mongoose#5.7.32
class Member {
#prop({ required: true })
public email!: string;
}
#index({ "members.email": 1 }, { unique: true }) // added unique to be easily testable
class Group {
#prop({ type: Member })
public members?: Member[];
#prop()
public name?: string;
}
const GroupModel = getModelForClass(Group);
(async () => {
await mongoose.connect(`mongodb://localhost:27017/`, { useNewUrlParser: true, dbName: "verifyMASTER", useCreateIndex: true, useUnifiedTopology: true });
await GroupModel.create({ name: "group1", members: [{ email: "h#h.h" }] });
try {
await GroupModel.create({ name: "group2", members: [{ email: "h#h.h" }] });
console.log("didnt fail");
} catch (err) {
// it should error
console.log("err", err);
}
console.log(await GroupModel.listIndexes());
await mongoose.disconnect();
})();
output of GroupModel.listIndexes:
[
{ v: 2, key: { _id: 1 }, name: '_id_', ns: 'verifyMASTER.groups' },
{
v: 2,
unique: true,
key: { 'members.email': 1 },
name: 'members.email_1',
ns: 'verifyMASTER.groups',
background: true
}
]