Add a new field to the Typegoose subdocument - mongodb

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

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.

Querying many to one on mongoose

Supposed I have two model User and Car, and they have one to many, many to one relationship
User Model
#modelOptions({ schemaOptions: { collection: 'users' } })
export class User extends TimeStamps {
#prop({ required: true })
name!: string
#prop({ required: true })
age!: number
#prop({ ref: () => Car })
cars?: Ref<Car>[]
}
Car Model
#modelOptions({ schemaOptions: { collection: 'cars' } })
export class Car extends TimeStamps {
#prop({ required: true })
model!: string
#prop({ required: true })
plateNumber!: string
#prop({ required: true })
type!: string
#prop({ ref: () => User })
user?: Ref<User>
}
I can save many cars into user using
async attachCarsToUser(userId: string, carsId: string[]) {
return this.findOneAndUpdate(
{ _id: userId },
{
$addToSet: {
cars: {
$each: carsId
}
}
}
)
}
and i can get the result by using:
async getAllUsers() {
return await this.usersRepository.find().populate('cars').lean()
}
Now, I can get the result correctly as it saved many cars on the user.
My question is how can I get like a bidirectional relationship on these two models such that when I query using these code:
async getAllCars() {
return await this.carsRepository.find().populate('user').lean()
}
I want to get the specific user on a car, however it doesn't work that way. and it produce result like this even if theres a field for user and also I already used the populate()
Im fairly new to monggodb, any idea what's the approach to this? Do i need to save the id of user on a car too?
BTW I used typegoose and under the hood it uses mongoose.

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
}
]

Typegoose - ObjectId that references a Sub-Document

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.