How to query another collection per document in mongoDB - node.js

Sorry the title isn't really clear, I was struggling a bit with it.
I'm trying to get posts from all a user's followers using thier IDs
PostModel.find({ author: { $in: arr } }).limit(150).sort({ createdAt: -1 }).exec();
and get the top comments (determined by the number of likes) for each post, the schema for comments is below
Comment Schema
const commentSchema: Schema = new Schema({
author: { type: Schema.Types.ObjectId, ref: 'User' },
parentPost: {type: Schema.Types.ObjectId, ref: 'Post'}, // Post a user is commenting on
createdAt: { type: Date, default: Date.now() },
likedBy: [{type: Schema.Types.ObjectId, ref: 'User'}],
likes: { type: Number, default: 0 },
});
Most of possible solutions I've come across has to do with agreggation but I'm not sure how to apply that here or if it's applicable, please how do I go about this ?

It is quite applicable with $lookup stage in aggregation: Perform a Single Equality Join with $lookup

Related

How to remove left-over ObjectId?

The problem is:
I have a collection of photos schema and likes schema, and inside photos there is an array of like ObjectIds called likeArray which is used to populate data from likes colletion.
But when i delete a like from likes collection, the ObjectId of that like in the likeArray still exists.
I tried to find the index of like._id in the likeArray and use likeArray.splice(index, 1) but couldn't work.
Can someone know the solution?
Here's the photo schema:
var Photo = mongoose.model('Photo', new mongoose.Schema({
photo_url: String,
photo_description: String,
user:{
id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
},
username: String,
profile_photo: String
},
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'comment'
}
],
likes: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'like'
}
],
created_at: {type: Date, default: Date.now}
}));
Here's the Like schema:
var Like = mongoose.model('Like', new mongoose.Schema({
user_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
},
photo_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'photo'
},
created_at: {type: Date, default: Date.now}
}));
Instead of splice you can use $pull operator. Here's how it'd look in mongo shell.
db.likes.remove({_id: like_oid});
db.photos.update({likes: like_oid}, {$pull: {likes: like_oid}}, { multi: true });

Mongoose query from two schemas that share same reference

Suppose I have two schemas:
1. UserEnrolledCourses
var userCoursesSchema = new mongoose.Schema({
user: { type: mongoose.Schema.Types.ObjectId, ref: 'users'},
courseId: { type: mongoose.Schema.Types.ObjectId, ref: 'courses'},
isEnrolled: Boolean,
});
2. CourseResources
var resourcesSchema = new mongoose.Schema({
courseId: { type: mongoose.Schema.Types.ObjectId, ref: 'courses', required: true },
type: {type:String, required:true},
});
Both of them shared the same courseId reference from courses schema.
So, my aim is to generate result from query that for each courseId that one user enrolled, list all of the resources that available. Is that possible?
In mongoDB, you are performing queries on one concrete collection. The only exception is the left outer join with new method $lookup in aggregation for mongodb 3.2 and more. Look at documentation

Can I populate ref's in a mongoose.js model instead of everytime I query?

Intro: I am creating a StackExchange clone using Node and Mongo to learn the language. I am currently working on the API.
I have the following 'questionSchema':
var questionSchema = new Schema({
_id : {type: String, default: shortid.generate},
title : {type: String, required: true},
question : {type: String, required: true},
user : {type: Schema.ObjectId, ref: 'User'},
points : {type: Number, default: 0},
date : {type: Date, default: Date.now},
answers : [answerSchema],
votes : [{
user: {type: Schema.ObjectId, ref: 'User', required: true},
vote: {type: Number, enum: [-1,0,1]}
}],
__v : {type: Number, select: false}
});
The idea is that when a user votes on a question the points field is incremented (or decremented) and the userid and vote added to the votes array. I have the vote array to detect if the user has already voted and prevent additional votes.
The problem: I'm having trouble actually checking if the user has voted (checking if their userid exists in the votes array). I have been playing around with adding the method 'hasVoted' to the questionSchema but:
I'm not sure how to actually make the check happen.
I'm also not sure if there is a way for me to filter the votes array during the query (at MongoDB) instead of after node gets the results.
This is my attempt at the method which I know is wrong:
//Has the user already voted on this question?
questionSchema.methods.hasVoted = function (userid, cb) {
this.votes.filter(function(vote) {
if(userid == vote._id) {
return '1';
} else {
return '0';
}
});
};
I would recommend to make vote schema like so
var voteSchema = new Schema({
user: {type: Schema.ObjectId, ref: 'User', required: true},
vote : {type: Number, required: true}
})
var questionSchema = new Schema({
_id : {type: String, default: shortid.generate},
title : {type: String, required: true},
question : {type: String, required: true},
points : {type: Number, default: 0},
date : {type: Date, default: Date.now},
answers : [answerSchema],
votes : [{type: Schema.ObjectId, ref: 'Vote', required: false}]
});
Then just get your question and go through all the votes.
QuestionSchema.findById(question.id)
.populate('votes')
.exec(function (err, question) {
// go through all the votes here
}
or query if there is an question with your user id inside the votes
QuestionSchema.find()
.and([{_id:questionId,},{votes.user:userId}])
.populate('votes') //dunno if you really have to populate i think you don't have to
.exec(function (err, user) {
// check if(user)
}
or do it like described here findOne Subdocument in Mongoose
//EDIT
or if you don't change your schema
QuestionSchema.find({votes.user:userId})
.exec(function (err, user) {
// should return ALL questions where the user id is in the votes your want a specific question do it in a and like in the example above
}
and if you only want that one element from the array you have to make a projection like described here How to find document and single subdocument matching given criterias in MongoDB collection

MongoDB queries to find or update in array doesn't work properly

I'm working on a new project, first time with huge collection of messages with the schema below -
mongoose.model('Messages', {
owner: { type: Schema.Types.ObjectId, ref: 'User' },
messages: [{
with: { type: Schema.Types.ObjectId, ref: 'User' },
from: {id: { type: Schema.Types.ObjectId, ref: 'User' }, user: String},
to: [{id: { type: Schema.Types.ObjectId, ref: 'User' }, user: String}],
sent: {type: Date, default: Date.now},
message: String,
seen: {type: Boolean, default: false},
stared: {type: Boolean, default: false}
}]
})
most of the used queries i use are to find messages of user (owner) with other user (messages.with) where sent date (messages.sent) is greater than date -
Messages.findOne({owner: owner.id, "messages.with": user.id, messages.sent: {$gt: mydate}},{messages: 1, owner: 1}, function (err, doc){}..)
I should get the document only if there's message greater (after) the date i entered else get nothing.
The date i use in the query is set to new Date('2015-12-04T03:39:23.126Z') for example so i don't see any problem with date format
and so with update -
Messages.update({owner: owner.id, "messages.with": user.id}, {$set:{"messages.$.seen": true}}, function(err, numEffected) {})
just doesn't work.
for now i check the data with another function, but just unnecessary.
Please help me figure it out
Thanks
update don't work, i faced the issue, when i use findOneAndUpdate(conditions, update, callback). then it worked for me

MongoDB performances $ref vs embedded

I recently started a project using mongodb and nodejs to build a restful web service. Unfortunately mongodb is very new to me, and coming from the relational databases world I'm asking my self a lot of questions.
Let me explain you my problem :
The goal is to build a sort of content management system with social features like a user can post topics that can be shared and commented.
I have 2 possibilities to do this the one using a reference to get topics posted by a user, the second using topics as embedded document of user instead of reference.
So basically I can have these 2 schemas :
var UserSchema = new Schema({
username: {
type: String,
unique: true,
required: true
},
password: {
type: String,
required: true
},
name: {
type: String
},
first_name: String,
phone: String,
topics: [Topic.schema]
});
var TopicSchema = new Schema({
_creator: {
type: String,
ref: 'User'
},
description: String,
comments: [Comments.schema],
shared_with: [{
type: Schema.ObjectId,
ref: 'User'
}] //[{ type: String, ref: 'User'}]
});
var CommentSchema = new Schema({
_creator: {
type: String,
require: true
},
text: {
type: String,
required: true
},
});
and
var UserSchema = new Schema({
username: {
type: String,
unique: true,
required: true
},
password: {
type: String,
required: true
},
name: {
type: String
},
first_name: String,
phone: String,
topics: [{ type: Schema.ObjectId, ref: 'Topics'}]
});
var TopicSchema = new Schema({
_creator: {
type: String,
ref: 'User'
},
description: String,
comments: [Comments.schema],
shared_with: [{
type: Schema.ObjectId,
ref: 'User'
}] //[{ type: String, ref: 'User'}]
});
var CommentSchema = new Schema({
_creator: {
type: String,
require: true
},
text: {
type: String,
required: true
},
});
So the first schema uses 1 collection of user document and the second use 1 collection for the user and 1 collection for the topics, this implies to make for example, 2 finds queries to retrieve a user and it's topics but it is also easyer to query directly the topics.
Here is the request I use to retrieve a specific topic with some user info with the first schema :
User.aggregate([
{$match: {
"topics._id":{$in:[mongoose.Types.ObjectId('56158c314861d2e60d000003')]}
}},
{ $unwind:"$topics" },
{$match: {
"topics._id":{$in:[mongoose.Types.ObjectId('56158c314861d2e60d000003')]}
}},
{ $group: {
_id: {
_id:"$_id",
name:"$name",
first_name:"$first_name"
},
topics:{ "$push": "$topics"}
}}
]);
So the question is, what do youh think ? Which is the good schema in your opinion ?
Thanks in advance.
Better solution: using a reference to get topics posted by a user
For this database use, one typically needs to consider the MMAPV1 document size limit (16MB). Putting user, topic, and comments in one document allows the document to grow without bound. If each topic is a page of text (1K), then each user could have about 16,000 topics before the limit is reached. That seems huge, but what happens if you decide to put images, videos, sounds in the topic as the product matures? Converting from an embedded to a normalized schema later would be a lot more work than a simple design choice today.
Similarly, if the comments could grow to cause a topic to exceed the 16MB limit, they should be in a separate collection. Unlikely? Probably. But if you are writing something that will become, say, the Huffington Post - check out comments on their popular articles.
Here is mongo's advice on data model design

Resources