Mongoose populate list of references - node.js

Mongoose (v3.8) + Node.js
I have a model called 'Products' which has this field:
upvoted_by: [{
user_id: { type: Schema.ObjectId, ref: 'Users' },
timestamp: { type: Date }
}]
And the Users model has a bunch of other fields.
To add an upvote, I do this:
product.upvoted_by.push({
user_id: req.user,
timestamp: new Date()
});
I'm trying to populate the upvoted_by.user_id field so that it contains the corresponding user data.
I tried this but it doesn't seem to work:
// product (= a document) has been found before
product.populate('upvoted_by.user_id', {
select: 'username' // username is a field in the Users model
}, function(err, doc) {
console.log(JSON.stringify(doc));
});
Any idea what's going wrong and how to fix it?

As per the docs, the following syntax should work, where select is an attribute of the Object passed to the populate method.
product.populate({path:"upvoted_by.user_id",
select:"username"}).exec(function(err, doc) {
console.log(err);
console.log(JSON.stringify(doc));
});

I ended up doing this:
product.populate({
path: 'upvoted_by.user_id',
select: 'username profile.picture profile.name'}, function(err, doc) {
console.log(JSON.stringify(doc.upvoted_by));
});

Related

node js mongoose delete a from an id in document array

I've two collection of reference both collection together. One of the collection is user and the other collection is project.
So, a user can add project to the projects collection, then one of the user type called supervisor can add staff to the project and the project id saved to the user collection which referred to staff document on the project collection.
So actually i need to do when admin deletes a supervisor from the user collection it deletes all the projects created by supervisor users's id that equal to addedBy documents which deleted from the users collection.
So my problems is when i do this process i need to delete all the project id is equal to the users collection projectId. it's an array and I tried to do this to many times but i couldn't find a solution. I'll provide all of the source code. That i created for this project.
Users collection
const userSchema = new Schema({
firstName: {
type: String
},
lastName: {
type: String
},
email: {
type: String
},
username: {
type: String
},
password: {
type: String
},
usertype: {
type: Schema.ObjectId,
ref: 'usertypes'
},
projectId: [{
type: Schema.ObjectId,
ref: 'projects'
}]
});
Project collection
const proSchema = new Schema({
projectName: {
type: String
},
description: {
type: String
},
addedBy: {
type: Schema.ObjectId,
ref: 'users'
},
staff: [{
type: Schema.ObjectId,
ref: 'users'
}]
});
Here is the query that i tried to do the process that i mentioned in above
Users
.findByIdAndRemove({
_id: req.params.id
})
.then(function(){
Projects
.remove({
userId: req.params.id
})
.then(function(err, project){
if(err) throw err;
console.log(project.id)
Users
.update({}, {
$pull: {
projectId: project.id
}
}, function(){
res.json({
success: true,
message: "Deleted"
});
});
});
});
I think the problems are
(1) Model.findByIdAndRemove only expects the ID (not the condition) i.e. Users.findByIdAndRemove(req.params.id) instead of Users.findByIdAndRemove({ _id: req.params.id })
(2) Model.remove's callback does not have a second argument in Projects.remove({ userId: req.params.id }).then(function (err, project) {. As well, you don't have a userId field in your ProjectSchema.
I would do
// delete user
Users.findByIdAndRemove(req.params.id, function (err, user) {
console.log('deleting user', user);
if (err)
throw err;
// delete user's projects
Projects.remove({ addedBy: user._id }, function (err) {
console.log('deleting projects');
if (err)
throw err;
// delete project references
Users.update({}, { $pull: { projectId: { $in: user.projectId }}}, function (err) {
console.log('deleting project references');
if (err)
throw err;
res.json({ success: true, message: "Deleted" });
});
});
});
(3) user.projectId is an array of ObjectIDs, so you need to use $in (see first example).
Aside: projects is a better name than projectId. The latter is ambiguous because a user has multiple projects not projectIds.
User.findByIdAndUpdate(req.params.idRec,
{ $pull: { comments: { _id: comm._id } } },
function (err, doc) {
if (!err) {
res.status(200).send()
} else {
res.render('error', { error: err })
}
})

Express +Mongoose push object into array of objects and adding two more rows into the object while inserting

I want to add a user inside the array objects, and add two more rows while inserting.
These are the two mongoose models that are used.
module.exports = mongoose.model('Users', {
id:String, //the same id as user.id
nick:String, //the same nick as user.nick
});
module.exports = mongoose.model('Stores', {
id: String,
user: [{
id: String,
nick: String,
nr: Number,
earnings: Number
}],
total: Number
});
So let's say I want to insert a Users that is found by its id(not the auto-generated). (I have the removed the if (err) to make it readable).
This how i try to solve right now.
Users.findOne({id : req.body.userid }, function(err, user) {
//what user contains
user = { _id: 551fb0b688eacdf0e700000c,
id: '123abc',
nick: 'Foo',
__v: 0 }
//I want to add this into the user and push it into exsisting array of
objects that is 'users'
//But before that i want to add more info to the user,
//the desired format that I want to insert into 'users'
user = {id: '123abc',
nick: 'Foo',
nr: req.body.nr, //new
earnings: req.body.earnings} //new
Stores.update({id: req.params.id},
{$push: { 'users' : user }}, function(err, store) {
});
});
The current result is the following.
users: [
{
id: "123abc",
nick: "Foo"
}]
How can I solve this?
The schema design as is creates at least one problem. What if a user updates their nick? Rather than only updating the Users collection you would also need to update every document in Stores that matches the user. You could use a ref and then populate to negate this concern.
module.exports = mongoose.model('Users', {
id: String, //the same id as user.id
nick: String, //the same nick as user.nick
});
module.exports = mongoose.model('Stores', {
id: String,
users: [{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Users'
},
nr: Number,
earnings: Number
}],
total: Number
});
Now the query would be:
Users.findOne({
id: req.body.userid
}, function(err, user) {
Stores.update({
id: req.params.id
}, {
$push: {
'users': {
user: user,
nr: req.body.nr, //new
earnings: req.body.earnings
}
}
}, function(err, store) {});
});
Later when you need to query Stores:
Stores
.find(QUERY)
.populate('users')
.exec(function(err, stores) {...
});

Updating 2 mongoose schemas in an api call

Currently I'm trying to update Two different User Schema's in an api call.
The first schema is logged in user schema, we give it a name = Tom
The second schema is other users who signup for the app, we give it a name = John
The schema code
schema.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt-nodejs');
var UserSchema = new Schema({
name: String,
username: { type: String, required: true, index: { unique: true }},
password: { type: String, required: true, select: false },
followers: [{ type: Schema.Types.ObjectId, ref: 'User'}],
following: [{ type: Schema.Types.ObjectId, ref: 'User'}],
followersCount: Number,
followingCount: Number
});
module.exports = mongoose.model('User', UserSchema);
The api name is '/follow/:user_id', what I want to achieve is . Whenever user Tom follows other user's like John, Tom's following field will be updated as well as John's follower field.
My current attempt (req.decoded.id is the logged in user)
api.js
// The first way
apiRouter.post('/follow/:user_id', function(req, res) {
User.findOneAndUpdate(
{
_id: req.decoded.id,
following: { $ne: req.params.user_id }
},
{
$push: { following: req.params.user_id},
$inc: { followingCount: 1}
},
function(err, currentUser) {
if (err) {
res.send(err);
return;
}
console.log(currentUser);
});
User.findOneAndUpdate(
{
_id: req.params.user_id,
followers: { $ne: req.decoded.id }
},
{
$push: { followers: req.decoded.id },
$inc: { followersCount: 1}
}, function(err, user) {
if(err) {
res.send(err);
return;
}
res.json({
message: "Successfully followed"
});
}
)
});
//Second way
apiRouter.post('/follow/:user_id', function(req, res) {
// find a current user that has logged in
User.update(
{
_id: req.decoded.id,
following: { $ne: req.params.user_id }
},
{
$push: { following: req.params.user_id},
$inc: { followingCount: 1}
},
function(err) {
if (err) {
res.send(err);
return;
}
User.update(
{
_id: req.params.user_id,
followers: { $ne: req.decoded.id }
},
{
$push: { followers: req.decoded.id },
$inc: { followersCount: 1}
}
), function(err) {
if(err) return res.send(err);
res.json({ message: "Successfully Followed!" });
}
});
});
Both have problems,
The first way: The problem is, 'Can't set headers that already sent', because of the two separate mongoose query in one api call, it response twice that's why I get that error.
The second way: The problem is, the following field of logged in user(Tom) gets updated while the other user's followers field (John) return null. I console log both value and as well test it with POSTMAN chrome app.
Lend me your thoughts fellas!
The first route you took seems to be fine.
However, as #cdbajorin mentioned, the error "can't send headers that already sent" has nothing to do with mongoose but the fact that you're trying to set the header after sending a response to the client already. (see this lovely answer)
My suggestion would be to make sure that both database calls are successful before you send a response.
You may also want to look into a two phase commit in this situation, as MongoDB does not support traditional DB transactions and you're updating two documents, one at a time. If for some reason either database call fails, a procedure to recover to a stable state should be taken.
The first way can be improved in two ways. One is updating followers field inside the callback of updating following field. The other way is using async-waterfall. I suggest to go with async-waterfall(npm async-waterfall).
The second way it is correct (could be improved running both of them in parallel) I guess the problem is in another place. I don't know which framework you are using but i guess the field _id is from mongoDB and is an ObjectId and looks like that the decoded.id can be an objectId while the one that comes from the request is of course just a string. So I guess it is empty because it does not find any user with that string.
Try do make it an objectId out of that string ( reffering to req.params.user_id in the second query)

Insert into embedded document

I have the following schema:
var UserSchema = new Schema({
username: { type: String, required: true },
password: { type: String, required: true },
userType: { type: String, default: 'user'},
quizzHistory: [{
quizzName: String,
quizzScore: Number
}]
});
my goal is to change document into embedded quizzHistory or insert new one if not exists document in embedded quizzeHistory
I try to set document into embedded quizzHistory :
User.findOneAndUpdate({ _id: req.session.user['_id'], 'quizzHistory.quizzName': testName},
{
'$set': {
'quizzHistory.$.quizzName': testName,
'quizzHistory.$.quizzScore': finalScore
}
}, {upsert: true},
function(err, upd) {
console.log("added");
})
code above works if there is document in quizzHistory with required _id and quizzHistory.quizzName,but don't pushed new one if there isn't any document.
Is there any way in Mongodb to change document into embedded collection or insert new one if not exists ?
the reason is because you are using "find and update" you are not handling the condition when the row hasn't been found and create a new document, being said that you need manage the in a different way like
User.update({ _id: req.session.user['_id'], 'quizzHistory.quizzName': testName},
{
'$push': {
'quizzHistory.quizzName': testName,
'quizzHistory.quizzScore': finalScore
}
}, {upsert: true},
function(err, upd) {
console.log("added");
})
this worked for me
User.update({ _id: req.session.user['_id'],'quizzHistory.quizzName':testName},
{
$set: {
"quizzHistory.$.quizzName":testName,
"quizzHistory.$.quizzScore":finalScore
}
},
function(err, upd) {
if(!upd){
User.update({ _id: req.session.user['_id']},
{ "$addToSet": { quizzHistory: newScoreData }},function(err,data){
});
}
});
If you want to benefit for all possible plugins and methods added to model and don't want to fight with actual Mongo queries you should first retrieve user object, then push new element to quizzHistory array and do save. Something like below (you need to align that code to your needs).
var entry = {
quizzName : 'abc',
quizzScore : 123
};
User.findOne({_id:id, function(err, object) {
if(err) {
return someErrorCallback();
}
var
object.quizzHistory.push(entry);
object.save(function(err) {
if(err) {
return someErrorCallback();
}
someSuccessCallback();
});
});
Updating directly may be efficient, but questions usage of mongoose.
Hope it makes sense.

Dealing with $in using mongoose

This is the code:
usermodel.findOne({ user: req.session.user }, function (err, user){
usermodel.find({ _id: {$in: user.follow } }, { user: true }, function (err, users){
usermodel.find({ author: { $in: users.user }}, function (err, images){
console.log(users);
console.log(images);
});
});
And this is the Schema:
var userschema = new mongoose.Schema({
user: String,
follow: [String],
message: [{
title: String,
author: String,
message: String
}],
});
The followarray contains the _ids of the users that the actual user is following. With the first usermodel.find y get the follow array. With the second usermodel.find, I get the users names:
[ { _id: 50fd9c7b8e6a9d087d000006, user: 'Mrmangado' },
{ _id: 50fd9d3ce20da1dd7d000006, user: 'kirbo' } ]
And, with the third and last usermodel.find, I'm trying to get the message of the users. I get the users' names in the previous usermodel.find, and the author of the message has the same value of the user that has created it. The problem is the way I get the users' names, I think I have to have the users' names like this way:
[{ user: 'Mrmangado' },
{ user: 'kirbo' }]
If I receive an array like this, the $in statement will work perfectly. Is there any way to get an output like this...?
Thank's advance!
Are you sure that's what you want? The author field is a user's name, not its _id? If so, you could just map the resulting object to get the name.
var usernames = users.map(function(u) { return u.user; });
//= ["Mrmangado", "kirbo"]

Resources