Mongoose Virtual reference between two ObjectIds - javascript

I know it is possible to create a virtual reference using a local _id to a foreign ObjectId, but is it possible to use a local ObjectId to a foreign ObjectId?
I'm trying to figure out if I have a bug somewhere or if this is not possible because it's not working.
const Post = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user',
},
text: {
type: String,
}
})
const ProfileSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user',
},
bio: {
type: String,
max: 500,
}
});
ProfileSchema.virtual('posts', {
ref: 'Post',
localField: 'user',
foreignField: 'user',
});
// query
const profile = await Profile.findOne({
user: req.user.id,
}).populate('posts')

Yes it is possible, but you need to add { toJSON: { virtuals: true } } to the Schema.
const ProfileSchema = new mongoose.Schema(
{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user',
},
bio: {
type: String,
max: 500,
},
},
{
toJSON: { virtuals: true },
}
);
From docs:
Keep in mind that virtuals are not included in toJSON() output by default. If you want populate virtuals to show up when using functions that rely on JSON.stringify(), like Express' res.json() function, set the virtuals: true option on your schema's toJSON options.
https://mongoosejs.com/docs/populate.html#populate-virtuals

Related

Mongoose getting the error: MissingSchemaError: Schema hasn't been registered for model

I am getting this error and can't figure it out. I HAVE NAMED THE REF EXACLTY AS THE MODEL:
MissingSchemaError: Schema hasn't been registered for model "ParticipantStatus".
Here are my models:
ParticipantStatus model:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const participantStatusSchema = new Schema({
_id: {
type: Number,
required: true,
},
name: {
type: String,
required: true,
},
});
module.exports = mongoose.model('ParticipantStatus', participantStatusSchema);
EventParticipant model:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const eventParticipantSchema = new Schema(
{
userId: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true,
},
eventId: {
type: Schema.Types.ObjectId,
ref: 'Event',
required: true,
},
teamId: {
type: Schema.Types.ObjectId,
ref: 'Team',
},
statusId: {
type: Number,
ref: 'ParticipantStatus',
required: true,
},
isActive: {
type: Boolean,
required: true,
default: true,
},
},
{ timestamps: true }
);
module.exports = mongoose.model('EventParticipant', eventParticipantSchema);
This is the code where the error is thrown, when i try to get event participants and populate statusId(ParticipantStatus):
let participants = await EventParticipant.find(
{
eventId: req.params.eventId,
isActive: true,
},
{ _id: false }
)
.populate('userId', 'email')
.populate('statusId')
.select('userId');
I have been stuck for hours on this. Only the .populate('statusId') part throws the error, the rest works well. Thanks in advance
type of field that you want to populate based on should be ObjectId so change the type of statusId from Number to Schema.Types.ObjectId like this:
statusId: {
type: Schema.Types.ObjectId,
ref: 'ParticipantStatus',
required: true,
}
Well the problem was that u need to import:
const ParticipantStatus = require('../models/participantStatus.model');
even if you do not REFERENCE IT DIRECTLY in your code, which is really strange in my opinion.
Hope this helps anyone.

Populate many subdocument levels down in mongoose

I'm creating a comment system where a comment can have subComments (when you comment on a comment). I can say how many levels deep I want to populate the subComments, but I don't know how many there are in advance. Is there a way to tell mongoose to just keep populating the subComments of already populated comments until there are no more subDocuments?
CommentModel.js
const mongoose = require("mongoose");
const schema = mongoose.Schema;
const commentSchema = new schema(
{
post: { type: schema.Types.ObjectId, required: true, ref: "post" },
content: { type: String, required: true },
votes: { type: Number, required: true },
user: { type: schema.Types.ObjectId, required: true, ref: "user" },
subComments: [{ type: schema.Types.ObjectId, ref: "comment" }],
parentComment: { type: schema.Types.ObjectId, ref: "comment" },
},
{ timestamps: true }
);
module.exports = Comment = mongoose.model("comment", commentSchema);
PostRouter.js
router.get("/full/:postId", async (req, res) => {
const postId = req.params.postId;
const post = await Post.findById(postId).populate({
path: "comments",
populate: {
path: "subComments",
},
// how can i populate infinitely down in the path subComments?
});
res.json(post);
});
Please check the graph lookup feature: https://docs.mongodb.com/manual/reference/operator/aggregation/graphLookup/
Example with arrays can be found here: https://www.oodlestechnologies.com/blogs/how-to-use-graphlookup-in-mongodb/

User, post, and comment model in mongoose

I am making a forum app that is quite similar to Reddit. I am using mongoose here for schema design. I have some doubts regarding models that I have- User, Post and Comment.
Does the schema look all right of all the models?
In User, I want to show the friends on the user profile. Like when you go to his profile, there will be like Friends(200), and when I click the friend list will appear(like Facebook, then on clicking you can access friends' profiles). I haven't created Friend schema, but does it needed actually?
Question about comments. So far, I have the basic comment schema. What happens to the threaded comments or replies. How do I store them?
All the models that I have currently:
const userSchema = new Schema(
{
username: { type: String, required: true },
email: { type: String, reuired: true },
password: { type: String, required: true },
country: { type: String, required: true },
friends: [{ type: Schema.Types.ObjectId, ref: "Friend" }], // is it needed?
posts: [{ type: Schema.Types.ObjectId, ref: "Post" }],
},
{ timestamps: true }
)
const postSchema = new Schema(
{
title: { type: String, required: true },
description: { type: String, required: true },
user: { type: Schema.Types.ObjectId, ref: "User" },
slug: { type: String, slug: "title" },
upvotes: { type: Number },
downvotes: { type: Number },
city: { type: String }, // post will have a tag by the city like NewYork, LA etc.
},
{ timestamps: true }
)
const commentSchema = new Schema(
{
comment: { type: String, required: true },
post: { type: Schema.Types.ObjectId, ref: "Post"},
user: { type: Schema.Types.ObjectId, ref: "User"},
upvotes: { type: Number },
downvotes: { type: Number }
},
{ timestamps: true }
)
For the Second Question you don't need friend schema since friend is also a user.
For the first question, Why do intend to store all the posts of a user inside the User object as a list of posts, this would mean fetching 100s of posts even when something basic like username or email is required. Instead, you can place user_id in the Post Schema, helping to identify posts from a particular User.

What is the best way to attach properties to mongoose documents

I have an array of posts and each post contains an author id. I want to loop through each post and find author from User model by using author id and then attach it to post. What is the best and efficient way to do it. I am currently doing it this way, but it decreases the performance. Thanks.
posts = await Promise.all(
posts.map(async post => {
post.author = await User.findById(post.author).lean();
return post;
})
);
// POST SCHEMA
const postSchema = new mongoose.Schema({
author: {
type: String,
required: true
},
body: {
type: String,
required: true
},
post_image: {
url: String,
public_id: String,
width: Number,
height: Number
},
date_created: {
type: Date,
default: Date.now()
}
});
// USER SCHEMA
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
register_date: {
type: Date,
required: true,
default: Date.now()
},
friends: {
type: Array,
default: []
}
});
// NEW POST SCHEMA
const postSchema = new mongoose.Schema({
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'users',
required: true
},
body: {
type: String,
required: true
},
post_image: {
url: String,
public_id: String,
width: Number,
height: Number
},
date_created: {
type: Date,
default: Date.now()
}
});
// USER SCHEMA
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
register_date: {
type: Date,
required: true,
default: Date.now()
},
friends: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'users',
required: true
}],
});
You can use auto population of mongo object in mongoose. It won't cause performance issues as it uses id index. Similar to this doc: https://mongoosejs.com/docs/populate.html
Your query will look like this:
const post = await Post.find({ author: { $in: req.user.friends }})
.populate('author')
.exec();
console.log(post);
Or you can use aggregate according to this document: https://mongoosejs.com/docs/api/aggregate.html
Your query will then look like:
const query = [
{ $match: { author: { $in: req.user.friends } } },
{ $lookup: { from: "users", localField: "author", foreignField: "_id", as: "authorDetails" } },
{ $unwind: "authorDetails" },
]
const post = await Post.aggregate(query).exec();
console.log(post);

Handlebars each statement can access data that handlebars won't access directly

I have a simple help desk app I've been building, where user can make request for site changes. One of the features is being able to see all request made by a specific person, which is working. However on that page I wanted to have something akin to "User's Request" where user is the person's page you are on. However I can't seem to get it to work without some weird issues. If I use:
{{#each request}}
{{user.firstName}}'s Request
{{/each}}
It works but I end up with the header being written as many times as the user has request. However, when I tried:
{{request.user.firstName}}
It returns nothing.
My route is populating user data, so I think I should be able to reference it directly. Here's the route:
// list Request by User
router.get('/user/:userId', (req, res) => {
Request.find({user: req.params.userId})
.populate('user')
.populate('request')
.then(request => {
res.render('request/user', {
request: request,
});
});
});
Here's the schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Create Schema
const RequestSchema = new Schema({
title: {
type: String,
required: true,
},
body: {
type: String,
required: true,
},
status: {
type: String,
default: 'new',
},
priority: {
type: String,
default: 'low',
},
project: {
type: String,
default: 'miscellaneous',
},
category: {
type: String,
default: 'change',
category: ['change', 'bug', 'enhancement', 'investigation', 'minor_task', 'major_task', 'question'],
},
organization: {
type: String,
default: 'any',
},
assignedUser: {
type: String,
default: 'venkat',
},
allowComments: {
type: Boolean,
default: true,
},
user: {
type: Schema.Types.ObjectId,
ref: 'users',
},
lastUser: {
type: Schema.Types.ObjectId,
ref: 'users',
},
date: {
type: Date,
default: Date.now,
},
lastUpdate: {
type: Date,
default: Date.now,
},
comments: [{
commentBody: {
type: String,
required: true,
},
commentDate: {
type: Date,
default: Date.now,
},
commentUser: {
type: Schema.Types.ObjectId,
ref: 'users',
},
}],
});
// Create collection and add Schema
mongoose.model('request', RequestSchema);
The rest of the code is at: https://github.com/Abourass/avm_req_desk
If anyone is wondering how, the answer was to add the array identifier to the dot path notation:
<h4>{{request.0.user.firstName}}'s Request</h4>

Categories

Resources