Check against multiple fields when using Mongoose Virtual schema? - node.js

I am a little stuck with trying to figure out the best way to populate a virtual schema in my db. In this instance, I want to check against multiple fields but I do not know any way to do this or alternative ways.
I had hoped I could just use some string arrays in the 'localField' & 'foreignField' keys of the virtual schema but this was not the case.
The user has a 'Series' _id saved, and the virtual schema 'leagues' gets all leagues that the user is entered into too, the catch is a league belongs to different Series. I want to only retrieve the leagues that the user is entered into & that match up with the user's series _id also...
As you can see currently this virtual schema just returns all the leagues the user is entered into regardless of the series. :(
Any ideas? I have been very confused about how to achieve this.
const schema: mongoose.Schema = new mongoose.Schema({
_id: {
type: mongoose.Schema.Types.ObjectId,
auto: true
},
username: {
type: String,
unique: true,
},
series: {
ref: 'Serie',
type: mongoose.Schema.Types.ObjectId,
autopopulate: {
maxDepth: 1,
select: ['title']
}
}
});
schema.virtual('leagues', {
ref: 'League',
localField: '_id',
foreignField: 'users',
autopopulate: {
maxDepth: 1,
select: ['title', 'engineSize', 'host', 'series']
}
});
The league schema looks like this
const schema: mongoose.Schema = new mongoose.Schema({
_id: {
type: mongoose.Schema.Types.ObjectId,
auto: true
},
title: String,
series: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Serie',
},
users: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
}]
});

This was my solution to checking against multiple fields in mongoose using a getter.
schema.virtual('motoduel').get(function () {
return motoduelModel.findOne({
event: this.series.upcomingEvent._id,
riderGroup: this.riderGroup._id
});
});

Related

Mongoose Virtual - Count references in another model that are in local array of references

I have 2 models Comment and Report.
const mongoose = require('mongoose');
const CommentSchema = new mongoose.Schema(
{
content: {
type: String,
trim: true,
maxLength: 2048,
},
createdAt: {
type: Date,
default: Date.now,
},
parent: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment',
required: false,
},
replies: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment',
},
],
isReply: {
type: Boolean,
default: false,
},
},
{ toJSON: { virtuals: true }, toObject: { virtuals: true } }
);
CommentSchema.virtual('reportCount', {
ref: 'Report',
localField: '_id',
foreignField: 'comment',
justOne: false,
count: true,
});
CommentSchema.virtual('reportReplyCount', {
ref: 'Report',
localField: 'replies',
foreignField: 'comment',
justOne: false,
count: true,
});
module.exports = mongoose.model('Comment', CommentSchema);
Comment has field replies which is array of references pointing to the Comment model. A User can report a comment, and when that happens a new Report document is stored in Report collection, and it contains a reference to that comment and a reference to a User. I have 2 virtual properties in the Comment Schema, reportCount (show number of reports for that comment) and reportReplyCount (shows number of reports on comment replies). Now the reportCount works flawlessly, but the reportReplyCount does not. When I create a comment, and the replies to that comment, it shows number of replies instead of number of reports. I googled but could not find anything similar.
const mongoose = require('mongoose');
const ReportSchema = new mongoose.Schema({
description: {
type: String,
trim: true,
required: true,
maxLength: 100,
},
reporter: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
createdAt: {
type: Date,
default: Date.now,
},
comment: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment',
required: true,
},
});
module.exports = mongoose.model('Report', ReportSchema);
I don't know what you're trying to do exactly, but I've looked around and there doesn't seem to be any existing solution for this. Virtuals are one way of solving the problem, but I haven't seen an answer that uses it in this scenario.
You could try to create a new virtual called reportReplyCount that shows the number of reports on replies. Then use aggregate on Comment and replace reportCount with the new virtual. You can use something like the following:
CommentSchema.virtual('reportReplyCount', {
ref: 'Report', // reference to Report model
localField: 'replies', // matches field Comment Schema has named 'replies'
foreignField: 'comment', // matches field Report Schema has named 'comment' (foreign key in Report model)
justOne: false, // this is going to return all related documents, not just one (just like reportCount)
count: true, // set it to true so it returns a number instead of an array of documents
});
CommentSchema.methods = { ... }
CommentSchema.statics = { ... }
module.exports = mongoose.model('Comment', CommentSchema);
I would avoid using virtuals in your case if you can find another solution for your problem.
As a side note, I have seen developers create a new model to act as the virtual, like this:
const mongoose = require('mongoose');
const ReportSchema = new mongoose.Schema({
description: {
type: String,
trim: true,
required: true,
maxLength: 100,
},
reporter: { // reference to User model (foreign key)
type: mongoose.Schema.Types.ObjectId,
ref: 'User', // reference to User model (foreign key)
});
module.exports = mongoose.model('Report', ReportSchema);
// Now you need an instance of that new Schema called VirtualReport. The schema must follow the same format as the "real" Report's schema did above but with a few extra parameters that refer to the virtual and it's definition (as in how it will behave).
const VirtualReportSchema = new mongoose.Schema({ ... }, { _id : false });
module.exports = mongoose.model('VirtualReport', VirtualReportSchema);
Then all you need to do is, in your schema that has the virtual:
// Now you can use VirtualReport like any other model. It will work just like Report but it won't get stored in the database.
CommentSchema.virtual('reportReplyCount', {
ref: 'VirtualReport', // reference to VirtualReport model
localField: 'replies', // matches field Comment Schema has named 'replies'
foreignField: 'comment', // matches field VirtualReport Schema has named 'comment' (foreign key in VirtualReport model)
justOne: false, // this is going to return all related documents, not just one (just like reportCount)
count: true, // set it to true so it returns a number instead of an array of documents
});
CommentSchema.methods = { ... }
CommentSchema.statics = { ... }
module.exports = mongoose.model('Comment', CommentSchema);
But please note that the virtual's definition ("how it will behave") must contain _id property set to false (otherwise an error will be thrown). This is because when virtuals are used in subdocuments and a user references them via dot notation (e.g., commentToBeDeleted[parent].reportReplyCount), dot notation tries to access _id property of the virtual. If it's set to false, dot notation won't be able to find that virtual and you'll get an error. So don't forget to set _id property to false!
BTW, this question was asked here. It's rather unfortunate that the question was asked on Stack Overflow instead of MongoDB's own docs where a link is provided to the explanation for virtuals (well there is also a comment about "justOne" but at least it refers directly to documentation).

How to populate fields present inside a map type schema in mongoose?

I have my mongoose schema as follow
const instructorSchema = new mongoose.schema({
coursesByMe: [
{
course: {
type: ObjectId,
ref: "Course",
},
submissions: {
type: Map,
of: Submission.schema,
},
},
],
active: {
type: Boolean,
default: false,
},
});
My submission schema is a simple schema
const submissionSchema = new mongoose.Schema({
user: {
type: ObjectId,
ref: "User",
},
subFile: String,
});
module.exports = mongoose.model("Submission", submissionSchema);
I want to populate the user field but I am unable to as the type of submissions is of map type schema.
Any help with this would be really grateful. Thank you.

Mongoose / Mongodb - should I explicitly add objectid to populate?

So I have 2 models, one is users and the other is sessions.
It was initially setup as:
const users = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
username: {type: String, unique: true},
}
);
module.exports = mongoose.model('User', users,);
I then went on and added another model, called sessions,
const sessions = mongoose.Schema({
_id: {type: mongoose.Schema.Types.ObjectId, default: mongoose.Schema.Types.ObjectId, null: false},
user_id: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
timestamp: {type: Date},
});
module.exports = mongoose.model('Sessions', sessions);
I then decided, I want to to be able to pull the user's sessions along with the users and added
sessions: [{type: mongoose.Schema.Types.ObjectId, ref: 'SurfSessions', default: null}] to my users model schema.
I quickly learnt that users.findOne(username:admin).populate('sessions') isn't gonna fill up sessions by itself, as sessions returns as an empty array [].
Since I don't have sessions inside users populated with objectid's does that mean I cannot populate sessions or is there another way?
You should be able to do this with virtual populate:
const users = new mongoose.Schema(
{
_id: mongoose.Schema.Types.ObjectId,
username: { type: String, unique: true },
},
{ toJSON: { virtuals: true }, toObject: { virtuals: true } }
);
module.exports = mongoose.model('User', users);
Then:
userSchema.virtual('sessions', {
ref: 'Session', // model to search in
foreignField: 'user_id',
localField: '_id', // checks where user_id on session === _id on user
});
After this, use .populate('sessions') as normal. For more check this out: https://mongoosejs.com/docs/tutorials/virtuals.html#populate

How to name a foreign key different from db name in mongoose

Is there a way in mongoose + Node.js/Express to define the relation between the foreign key field and what I refer to that field in the model is? My issue is that I have a mongo database where my foreign keys are all formatted like 'exampleId' instead of 'example'. I could just call out 'exampleId' directly but then it leads to weird things like when I populate 'exampleId' instead of 'example' (which is confusing because once populated, it is now the 'example' itself instead of its id).
Here is how I do it now and it works with my graphQL server, but only if my field in the database is 'course' while my database's field is 'courseId'
const CourseSchema = new Schema({
_id: { type: String },
sections: [{
type: Schema.Types.String,
ref: 'Section'
}],
});
const SectionType = new GraphQLObjectType({
name: 'SectionType',
fields: () => ({
id: { type: GraphQLID },
courseId: {
type: require('./course_type'),
resolve(parentValue) {
return Section.findById(parentValue)
.populate('course')
.then(section => section.course);
}
},
}),
});
I figured it out! With the newest version of mongoose, you actually can use virtual fields to accomplish what I wanted to do and this technique allows for flexibility in laying out your schema. Say that my MongoDB collections look like the following:
Courses { _id, sectionIds }
Lectures { _id, courseId }
I can use the following schema in mongoose and it will allow me to refer to course.lectures or lecture.course instead of the usual course.lectureIds or section.courseId:
const CourseSchema = new Schema({
_id: { type: String },
});
CourseSchema.virtual('sections', {
type: Schema.Types.String,
ref: 'Section',
localField: 'sectionIds',
foreignField: '_id',
justOne: false,
});
CourseSchema.statics.findSections = function(id) {
return this.findById(id)
.populate('sections')
.then(course => course.sections);
}
const SectionSchema = new Schema({
_id: { type: String },
});
SectionSchema.virtual('course', {
type: Schema.Types.String,
ref: 'Course',
localField: 'courseId',
foreignField: '_id',
justOne: true,
});
Actually MongoDB isn't a relational database. You can alter the field and its name whatever you like. Ex I Have an Owner(Meteor.users) table and Patient Table with this column
ownerid : {type: String, min: 1},
firstname: {type: String, min: 1},
lastname: {type: String, min: 1},
middlename: {type: String, min: 1, optional: true},
createdbyid: { type: String },
createdbyname: { type: String },
createdat: { type: Date, defaultValue: new Date() },
updatedbyid: { type: String, optional: true },
updatedbyname : { type: String, optional: true },
updatedat: { type: Date, defaultValue: new Date() },
I can easily stamp the value of my {Meteor.Users()._id} to ownerid of my designated patient by just processing them at meteor.methods. You don't have to worry about foreign keys mongo doesn't do relational databases you can customize your database whatever you like. I Hope this helps ;)
Mongoose Documentation posits that _id has to be used in refs and that[i]t is important to match the type of _id to the type of ref. , e.g.:
var personSchema = Schema({
_id : Number, //it is `Number`
name : String,
age : Number,
stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
var storySchema = Schema({
_creator : { type: Number, ref: 'Person' },
title : String,
fans : [{ type: Number, ref: 'Person' }] // use `Number` to match
});
I also wonder if by 'database' you mean 'collection'.

Mongoose Populate - array

can someone please help me with population of this schema? I need to populate array of Staff by their userId.
var PlaceSchema = new Schema ({
name: { type: String, required: true, trim: true },
permalink: { type: String },
country: { type: String, required: true },
...long story :D...
staff: [staffSchema],
admins: [adminSchema],
masterPlace:{ type: Boolean },
images: []
});
var staffSchema = new Schema ({
userId: { type: Schema.Types.ObjectId, ref: 'Account' },
role: { type: Number }
});
var adminSchema = new Schema ({
userId: { type: Schema.Types.ObjectId, ref: 'Account'}
})
var Places = mongoose.model('Places', PlaceSchema);
I tried to use this query, but without success.
Places.findOne({'_id' : placeId}).populate('staff.userId').exec(function(err, doc){
console.log(doc);
});
Polpulation is intended as a method for "pulling in" information from the related models in the collection. So rather than specifying a related field "directly", instead reference the related fields so the document appears to have all of those sub-documents embedded in the response:
Places.findOne({'_id' : placeId}).populate('staff','_id')
.exec(function(err, doc){
console.log(doc);
});
The second argument just returns the field that you want. So it "filters" the response.
There is more information on populate in the documentation.

Resources