Mongoose virtual with multiple foreignFields - node.js

I have a Schema Events which has a key creator which is a reference to the creator and a key musicians which is an array with the musicians.
A Musician can create Events or participate as a musician. I want to create a virtual property named events to the musician which will contain all the events that the musician has created and the events that participates.
I tried this:
MusicianSchema.virtual('events', {
ref: 'Events',
localField: '_id',
foreignField: ['creator, musicians'],
justOne: false,
});
but this returns an empty array. Is there any way to make it work, or mongoose doesn't have this feature (yet)?
EDIT
This is the Event Schema:
const eventSchema = {
title: {
type: String,
trim: true,
required: true
},
description: {
type: String,
trim: true
},
startDate: {
type: Number,
required: true
},
endDate: {
type: Number,
required: true
},
picture: {
type: String,
get: getPicture,
},
location: {
type: [Number],
index: '2dsphere',
get: getLocation
},
creator: {
type: mongoose.Schema.Types.ObjectId,
required: true,
refPath: 'creatorType'
},
musicians: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Musician'
}],
creatorType: {
type: String,
required: true,
enum: ['Musician', 'Entity']
}
};
And the Musicians Schema:
const musiciansSchema = {
name: {
type: String,
trim: true,
required: true
},
picture: {
type: String,
get: getPicture,
},
description: String,
type: {
type: String,
required: true,
enum: ['Band', 'Artist'],
},
};

Mongoose virtual with multiple foreignField feature is not available at the moment, Suggestion for new feature has already requested on GitHub,
For now you need to use separate virtual/populate
MusicianSchema.virtual('creatorEvents', {
ref: 'Events',
localField: '_id',
foreignField: 'creator',
justOne: false,
});
MusicianSchema.virtual('musiciansEvents', {
ref: 'Events',
localField: '_id',
foreignField: 'musicians',
justOne: false,
});

I was facing similar situation during development and what I did was to replicate the same code twice but with different virtual names and it works perfectly.
In your case:
​MusicianSchema​.​virtual​(​'createdEvents'​,​ ​{​
    ​ref​: ​'Events'​,​
    ​localField​: ​'_id'​,​
    ​foreignField​: ​'creator'​,​
    ​justOne​: ​false​,​
​}​)​;​
​MusicianSchema​.​virtual​(​'musicianEvents'​,​ ​{​
    ​ref​: ​'Events'​,​
    ​localField​: ​'_id'​,​
    ​foreignField​: ​'musician​,​
    ​justOne​: ​false​,​
​}​)​;​

Related

mongoose query, find where array of tags id includes at least one id from query tags array

I have article schema :
const schema = new Schema({
title: {
type: String,
unique: true,
required: [true, 'Title is required'],
},
slug: {
type: String,
slug: "title",
slugPaddingSize: 2,
unique: true
},
editor: {
type: String
},
duration: {
type: String
},
image: Object,
category: { type: Schema.Types.ObjectId, ref: 'Category' },
language: { type: Schema.Types.ObjectId, ref: 'Language' },
level: { type: Schema.Types.ObjectId, ref: 'Level' },
tag: [{ type: Schema.Types.ObjectId, ref: 'Tag' }]
},{
timestamps: true
})
Need to find every articles which tag contains at least one of tag id from req.query params (it can be also array of id's or just single id).
Thanks

Trouble with mongoDB Ref -> trying to ref array of diferent model

so i'm trying ref a array of daysOff (Ferias model) as a fiel in a User model,
i'm new to MongoDB and mongoose, and i'm trying to build a API that manages a company workers days off.
I apologyse in advance for the "format" of this question but it's the very first time that I post a question in StackOverflow :)
Thanks u all!
const mongoose = require("mongoose");
const moment = require("moment");
const feriasSchema = mongoose.Schema(
{
user: {
type: mongoose.Types.ObjectId,
required: true,
ref: "User",
},
firstName: {
type: String,
ref: "User",
},
lastName: {
type: String,
ref: "User",
},
workerNumber: {
type: Number,
ref: "User",
},
sectionOfWork: {
type: String,
ref: "User",
},
totalFerias: {
type: Number,
required: true,
},
//this is the field i want to add in User model as an array of (dates format DD/MM/YYYY)
ferias: {
type: [String],
//default: [Date],
required: true,
},
tipoFerias: {
type: String,
required: true,
},
modo: {
type: String,
required: true,
},
},
{ timestamps: true }
);
module.exports = mongoose.model("Ferias", feriasSchema);
// USER MODEL
const userSchema = mongoose.Schema(
{
firstName: {
type: String,
required: true,
},
lastName: {
type: String,
required: true,
},
email: {
type: String,
required: [true, "Please enter a valid email address"],
unique: true,
},
password: {
type: String,
required: [true, "Please enter a password"],
},
workerNumber: {
type: Number,
required: true,
unique: true,
},
sectionOfWork: {
type: String,
required: true,
},
chefia: {
type: String,
required: true,
},
//this is the field i want to be an array (from Ferias model)
ferias: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Ferias",
}],
role: {
type: String,
required: true,
},
},
{ timestamps: true }
);
module.exports = mongoose.model("User", userSchema);
what am i doing wrong?
i'm getting and empty array ...
in express I can send the questioned data separately but u want to send a json to some frontend with all the fields of a user, with the "ferias" array present.

How to set Primary keys in Sails js waterline database relationships

I have been studying relationships with sails JS waterline database from the official documentation. I however been having difficulty understanding how I am supposed to set my foreign keys just like I do in normal mysql relationships.
Please note that I have read the documentation here https://sailsjs.com/documentation/concepts/models-and-orm/associations before asking this question.
Let's say I have a model PersonalInfo.js
module.exports = {
attributes: {
fullName:{
type: 'string',
required: true
},
phone:{
type: 'string',
required: true
},
location:{
type: 'string',
required: true
},
age:{
type: 'integer',
required: true
},
email:{
type: 'string',
required: true
},
gender:{
type: 'string',
required: true
},
userId:{
type: 'integer',
required: true,
}
},
};
And I have another model Archived.js which looks like this
module.exports = {
attributes: {
userId: {
type: 'number',
required: true,
//unique: true,
},
comment:{
type: 'string',
required: true
},
createdBy:{
type: 'number',
required: true
}
},
};
An archived item has a personalInfo. Knowing fully well that both models contain userId property, I want to fetch archived items with the related personalInfo like this, how do I relate the primary keys?
var archived = Archived.find().populate('personal');
By default sails will generate primary key id if you don't specify any.
If you want custom data as your primary key, you can override the id attribute in the model and give a columnName
id: {
type: 'string',
columnName: 'email_address',
required: true
}
You can then find a record using:
await User.find({ id: req.param('emailAddress' });
Reference
In your case, it seems like each archived has a personalInfo. So that's one to one from archived side, but, one to many from personalInfo side. To model these relationships, in sails you can do something like:
personalInfo.js
module.exports = {
attributes: {
fullName:{
type: 'string',
required: true
},
phone:{
type: 'string',
required: true
},
location:{
type: 'string',
required: true
},
age:{
type: 'integer',
required: true
},
email:{
type: 'string',
required: true
},
gender:{
type: 'string',
required: true
},
userId:{
type: 'integer',
required: true,
},
archives: {
collection: 'archived',
via: 'info'
}
},
};
archived.js
module.exports = {
attributes: {
userId: {
type: 'number',
required: true,
//unique: true,
},
comment:{
type: 'string',
required: true
},
createdBy:{
type: 'number',
required: true
},
info: {
model: 'personalinfo' // sails works with small cases internally for models
}
},
};
Once you do this, creating an archive would be:
await Archive.create({
...
// Set the User's Primary Key to associate the info with the archive.
info: 123
});
Now you would finally be able to populate the info while querying.
var archived = Archived.find().populate('info');

mongodb aggregate $lookup vs find and populate

I have a Video Schema like this:
const VideoSchema = new mongoose.Schema({
caption: {
type: String,
trim: true,
maxlength: 512,
required: true,
},
owner: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true,
},
// some more fields
comments: [{
type: mongoose.Schema.ObjectId,
ref: 'Comment',
}],
commentsCount: {
type: Number,
required: true,
default: 0,
},
}, { timestamps: true });
and a simple Comment schema like this:
const CommentSchema = new mongoose.Schema({
text: {
type: String,
required: true,
maxLength: 512,
},
owner: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true,
},
videoId: {
type: mongoose.Schema.ObjectId,
ref: 'Video',
required: true,
index: true,
},
}, { timestamps: true });
and with schemas like this I'm able to perform any kind of find query on my Video collection and populate it with its comments:
Video.find({ owner: someUserId }).populate({ path: 'comments' });
My question is how necessary is it to keep comment ids inside video collection? given that I have indexed the videoId field in my Comment schema, how bad it would be (speaking of performance) to get rid of these comment ids and the count of them and use aggregation $lookup to find a video's comments like this:
Video.aggregate([
{
$match: {
owner: someUserId,
},
},
{
$lookup: {
from: 'comments',
localField: '_id',
foreignField: 'videoId',
as: 'comments',
}
}
])
How different are these in terms of performance?
Well there is no way the $lookup would be faster than having the list of the comment ids on the actual video object. I mean you have to do a whole other request to mongo to get them now. So performance wise obviously the lookup would add time. That is assuming you are not using mongoose populate to "convert" those comment ids into the referenced objects.
If you are then removing the comments from the video (as well as the actual count prop) and doing the lookup is the way to go. Since you are matching right away in your arg and then doing a simple lookup I do not see how this would be a bottleneck for you. Also you can optimize/change/tune your aggregation vie explain etc.
You video schema would be pretty clean that way:
const VideoSchema = new mongoose.Schema({
caption: {
type: String,
trim: true,
maxlength: 512,
required: true,
},
owner: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true,
},
// some more fields
}, { timestamps: true });

How to keep track of videos watched with Node.js and Mongodb

I'm building a MEAN stack video app (I'm pretty new to Node and Mongodb) and I need a way to keep track of videos watched. How do I do this?
I was thinking I could have an array of Ids in the user collection that references videos but I'd like to be able to return videos with a watched: true key value pair that's dependent on the user making the request. If this is a good way to do it, how do I return a key value pair that's dependent on another document in another collection?
User model:
let UserSchema = new mongoose.Schema({
email: {
type: String,
required: true,
minlength: 1,
trim: true,
unique: true,
validate: {
validator: VALUE => validator.isEmail(VALUE),
message: '{VALUE} is not a valid email'
}
},
password: {
type: String,
required: true,
minlength: 6
},
admin: {
type: Boolean,
default: false
},
vid_inprogress: {
type: mongoose.Schema.Types.ObjectId
},
vid_completed: [{ type : mongoose.Schema.Types.ObjectId, ref: 'Attachment' }],
tokens: [{
access: {
type: String,
required: true
},
token: {
type: String,
required: true
}
}]
});
Video Model:
var Video = mongoose.model('Video', {
url: {
type: String,
required: true,
minlength: 1,
trim: true
},
title: {
type: String,
required: true,
default: '',
trim: true
},
description: {
type: String,
default: '',
trim: true
},
img: {
type: String,
default: '',
trim: true
},
attachments: [{ type : mongoose.Schema.Types.ObjectId, ref: 'Attachment' }]
});
vid_completed on the User model is where I'd like to keep track of the video ids that have been watched. And the Video model is what would be returned with a key: value pair based on whether the video id is found in the user vid_completed array. Let me know if that makes sense. Thanks in advance!
I ended up just using an array in the User model like so:
vid_inprogress: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Video'
},
vid_completed: [{ type : mongoose.Schema.Types.ObjectId, ref: 'Video' }]
I'll just have to add watched: true on the front end. Let me know if you have a better way. I'd be interested to hear.

Resources