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
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.
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.
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[];
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
}
]
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.