Mongoose slice array, in populated field - node.js

I have the following mongoose schemas:
The main one is userSchema which contains an array of friends,
friendSchema. Each friendSchema is an object that contains an array of messageSchema. The messageSchema is the deepest object, containing the body of the message.
var messageSchema = new mongoose.Schema({
...
body: String
});
var conversationsSchema = new mongoose.Schema({
...
messages: [messageSchema]
});
var friendSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
conversation: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Conversation',
},
}, { _id : false });
var userSchema = new mongoose.Schema({
...
friends: [friendSchema]
});
When retrieving specific user's friend, I populate its friends profiles, and if a conversation exist, I populate the conversation too.
How can I slice conversations.messages array, which resides in the population of the conversationobject ? I don't want to return the whole messages.
var userId = req.userid;
var populateQuery = [{ path:'friends.user',
select: queries.overviewConversationFields },
{ path:'friends.conversation' }];
User
.find({ _id: userId }, { friends: 1 })
.populate(populateQuery)
.exec(function(err, result){
if (err) { next(err); }
console.log(result);
}
EDIT(1) : I tried
.slice('friends.conversation.messages', -3)
EDIT(2) : I tried in populate query
{ path:'friends.conversation', options: { 'friends.conversation.messages': { $slice: -2 } }
EDIT(3) : For now, I can achieve what I want, slicing the array after the query is executed. This isn't optimized at all.

A little workaround that works.
I didn't found how to $slice an array that resides in a populated field.
However, the $slice operator works perfecly on any array, as long as its parent document has'nt been populated.
1) I decided to update the conversationSchema by adding an array containing both user's Id involved in the conversation :
var conversationsSchema = new mongoose.Schema({
users: [type: mongoose.Schema.Types.ObjectId],
messages: [messageSchema]
});
2) Then, I can easily find every conversation my user participates to.
As I said, I can properly slice the messages array, because nothing has to be populated.
Conversation.find({ users: userId },
{ 'messages': { $slice: -1 }}, function(err, conversation) {
});
3) Finally all I have to do, is to query all friends and conversations separately, and put back everything together, with a simple loop and a _find.
That would do more or less the same procedure of a Mongo population
Using async.parallel for more efficiency :
async.parallel({
friends: function(done){
User
.find({ _id: userId }, { friends: 1 })
.populate(populateQuery)
.exec(function(err, result){
if (err) { return done(err);}
done(null, result[0].friends);
});
},
conversations: function(done){
Conversation.find({ users: userId }, { 'messages': { $slice: -1 }}, function(err, conversation) {
if (err) { return done(err); }
done(null, conversation)
});
}}, function(err, results) {
if (err) { return next(err); }
var friends = results.friends;
var conversations = results.conversations;
for (var i = 0; i < friends.length; i++) {
if (friends[i].conversation) {
friends[i].conversation = _.find(conversations, function(conv){
return conv._id.equals(new ObjectId(friends[i].conversation));
});
}
}
});
// Friends contains now every conversation, with the last sent message.

Related

updating nested documents in mongoDB(node.js)

i am trying to update a value in the object of my embedded schema(comments schema) whose value i had previously stored 0 by default. i have tried all the ways to update but none of the stackoverflow answer worked.
my code is
var checkedBox = req.body.checkbox;
User.updateOne({_id: foundUser._id},{$set :{comments:{_id :checkedBox,cpermission:1,}}},function(err,updatec){
if(err){
console.log(err);
}
else{
console.log("successfull");
console.log(updatec);
}
});
i had comment schema nested in user schema,here foundUser._id is the particular users id,and checkedBox id is the embedded objects particular id. previously my cpermission was 0,set by default,but now i want to update it to 1. although this is updating my schema,but deleting the previous images and comments in the schema aswell.
where am i going wrong?
here is my schema
const commentSchema = new mongoose.Schema({
comment: String,
imagename: String,
cpermission:{type:Number,default:0},
});
const Comment = new mongoose.model("Comment", commentSchema);
const userSchema = new mongoose.Schema({
firstname: String,
lastname: String,
email: String,
password: String,
comments: [commentSchema],
upermission:{type:Number,default:0},
});
userSchema.plugin(passportLocalMongoose);
const User = new mongoose.model("User", userSchema);
First, you need to convert checkbox in the array, as it will be a string if you select a single element
Then wrap it with mongoose.Types.ObjectId as a precaution
Then you can use arrayFilters to update multiple matching array elements
var checkedBox = req.body.checkbox;
if (!Array.isArray(checkedBox)) {
checkedBox = [checkedBox]
}
checkedBox = checkedBox.map(id => mongoose.Types.ObjectId(id))
User.updateOne(
{ _id: foundUser._id }, // filter part
{ $set: { 'comments.$[comment].cpermission': 1 } }, // update part
{ arrayFilters: [{ 'comment._id': {$in: checkedBox }}] }, // options part
function (err, updatec) {
if (err) {
console.log(err);
}
else {
console.log("successfull");
console.log(updatec);
}
});
your comment is the array of documents. if you want to update an element of an array must be select it. for that must be added another condition to the first section of updateOne then in seconde section use $ for update selected element of the array.
User.updateOne(
{_id: foundUser._id, 'comments._id': checkedBox},
{
$set: {'comments.$.cpermission': 1}
}
, function (err, updatec) {
if (err) {
console.log(err)
}
else {
console.log('successfull')
console.log(updatec)
}
})
for more information, you can read this document form MongoDB official website.
Array Update Operators
var checkedBox = req.body.checkbox;
User.updateOne(
{ _id: foundUser._id, "comment._id": checkedBox },
{ $set: { "comment.$.cpermission": 1 } },
function (err, update) {
if (err) {
console.log(err);
} else {
console.log("successfull");
console.log(update);
}
}
);

Call a related collection via populate

I try to call a related list of logs for a certain user via Mongoose populate. Who can help me with finishing the response?
These are the schemes:
const logSchema = new Schema({
logTitle: String,
createdOn:
{ type: Date, 'default': Date.now },
postedBy: {
type: mongoose.Schema.Types.ObjectId, ref: 'User'}
});
const userSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
}
logs: { type: mongoose.Schema.Types.ObjectId, ref: 'logs' }
});
mongoose.model('User', userSchema);
mongoose.model('logs', logSchema);
Inspired by the Mongoose documentary (see above) and other questions in relation to this subject I think I got pretty far in making a nice get. request for this user. I miss the expierence to 'translate it' to Express.
const userReadLogs = function (req, res) {
if (req.params && req.params.userid) {
User1
.findById(req.params.userid)
.populate('logs')
.exec((err, user) => {
if (!user) { }); // shortened
return;
} else if (err) {
return; // shortened
}
response = { //question
log: {
user: user.logs
}
};
res
.status(200)
.json(response);
});
} else { }); //
}
};
The response in Postman etc would be something like this:
{
"log": {5a57b2e6f633ce1148350e29: logTitle1,
6a57b2e6f633ce1148350e32: newsPaper44,
51757b2e6f633ce1148350e29: logTitle3
}
First off, logs will not be a list of logs; it will be an object. If you want multiple logs for each user, you will need to store is as an array: logs: [{ type: mongoose.Schema.Types.ObjectId, ref: 'logs' }]
From the Mongoose docs: "Populated paths are no longer set to their original _id , their value is replaced with the mongoose document returned from the database by performing a separate query before returning the results." In other words, in your query user.logs will be the logs document for each user. It will contain all the properties, in your case logTitle, createdOn, and postedBy.
Sending user.logs as json from the server is as easy as: res.json(user.logs). So your query can look like this:
const userReadLogs = function (req, res) {
if (req.params && req.params.userid) {
User1
.findById(req.params.userid)
.populate('logs')
.exec((err, user) => {
if (!user) { }); // shortened
return;
} else if (err) {
return; // shortened
}
res.status(200).json(user.logs)
});
} else { }); //
}
};
I hope this makes it a little bit clearer!

How to do nested find in mongoose?

I have a user model which has todolists field, in the todolists field I want to get the specific todolist by id. my query is like this:
User.find({_id: user._id, _creator: user, todoList: todoList._id}, 'todoLists') // how do I query for todoList id here? I used _creator this on populate query.
Can I also do a search on a Usermodel field like this?
User.todoLists.find({todoList: todoList._id})
I haven't tested this yet because I am still modifying my Graphql schema and I am new in mongoose.I would really appreciate Links and suggestions. Help?
Assuming your models looks like this:
const todoListSchema = new Schema({
item: { type: String },
}, { collection: 'todolist' });
const userSchema = new Schema({
todoList: [todoListSchema],
}, { collection: 'user' });
mongoose.model('user', userSchema);
mongoose.model('todoList', todoListSchema);
Now you have multiple ways to do that:
1. Using the array filter() method
reference
User.findById(_id, (err, user) => {
const todoList = user.todoList.filter(id => id.equals(tdlId));
//your code..
})
2. Using mongoose id() method
reference
User.findById(_id, (err, user) => {
const todoList = user.todoList.id(tdlId);
//your code..
})
3. Using mongoose aggregate
reference
User.aggregate(
{ $match: { _id: userId} },
{ $unwind: '$todoList' },
{ $match: { todoList: tdlId } },
{ $project: { todoList: 1 } }
).then((user, err) => {
//your code..
}
});

Removing references in one-to-many and many-to-many relationships in MongoDB using Mongoose and Node.js

I need to mention that I am totally aware of the fact that MongoDB is not a relational database in the first place. However it supports referencing other documents, hence some functionality should be supported, imo. Anyways, I have this relationship: a Company has many Departments and one Department belongs to one Company.
company.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var CompanySchema = new Schema({
name: {
type: String,
unique: true,
required: true
},
departments: [{
type: Schema.Types.ObjectId,
ref: 'Department'
}],
dateCreated: {
type: Date,
default: Date.now
},
dateUpdated: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Company', CompanySchema);
department.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var DepartmentSchema = new Schema({
name: {
type: String,
required: true
},
company: {
type: Schema.Types.ObjectId,
ref: 'Company'
},
dateCreated: {
type: Date,
default: Date.now
},
dateUpdated: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Department', DepartmentSchema);
Now, I am writing Node.js logic to manipulate this data using API. I get that if I create a new Department, I should add a reference to Company and I should create its reference in this Company's departments array. Simple. But what if a user changes the Company property of a Department? Say, the HR Department used to belong to Company A, but a user now moves it to Company B? We need to remove the reference to this department from Company A's array and push it to Company B. The same is when we want to delete a department. We need to find a company it belongs to and dis-associate it. My solution is working ATM, but seems rather clumsy.
routes.js
var Department = require('../../models/department'),
Company = require('../../models/company');
module.exports = function(express) {
var router = express.Router();
router.route('/')
.get(function(req, res) {
// ...
})
.post(function(req, res) {
// ...
});
router.route('/:id')
.get(function(req, res) {
// ...
})
.put(function(req, res) {
// First we need to find the department with the request parameter id
Department.findOne({ _id: req.params.id }, function(err, data) {
if (err) return res.send(err);
var department = data;
// department.name = req.body.name || department.name; Not relevant
// If the company to which the department belongs is changed
if (department.company != req.body.company._id) {
// We should find the previous company
Company.findOne({ _id: department.company }, function(err, data) {
if (err) return res.send(err);
var company = data;
// Loop through its departments
for (var i = 0; i < company.departments.length; i++) {
if (company.departments[i].equals(department._id)) {
// And splice this array to remove the outdated reference
company.departments.splice(i, 1);
break;
}
}
company.save(function(err) {
if (err) return res.send(err);
});
});
// Now we find this new company which now holds the department in question
// and add our department as a reference
Company.findOne({ _id: req.body.company._id }, function(err, data) {
if (err) return res.send(err);
var company = data;
company.departments.push(department._id);
company.save(function(err) {
if (err) return res.send(err);
});
});
}
// department.company = req.body.company._id || department.company; Not relevant
// department.dateUpdated = undefined; Not relevant
// And finally save the department
department.save(function(err) {
if (err) return res.send(err);
return res.json({ success: true, message: 'Department updated successfully.' });
});
});
})
.delete(function(req, res) {
// Since we only have id of the department being deleted, we need to find it first
Department.findOne({ _id: req.params.id}, function(err, data) {
if (err) return res.send(err);
var department = data;
// Now we know the company it belongs to and should dis-associate them
// by removing the company's reference to this department
Company.findOne({ _id: department.company }, function(err, data) {
if (err) return res.send(err);
var company = data;
// Again we loop through the company's departments array to remove the ref
for (var i = 0; i < company.departments.length; i++) {
if (company.departments[i].equals(department._id)) {
company.departments.splice(i, 1);
break;
}
}
company.save(function(err) {
if (err) return res.send(err);
});
// I guess it should be synchronously AFTER everything is done,
// since if it is done in parallel with Department.findOne(..)
// piece, the remove part can happen BEFORE the dep is found
Department.remove({ _id: req.params.id }, function(err, data) {
if (err) return res.send(err);
return res.json({ success: true, message: 'Department deleted successfully.' });
});
});
});
});
return router;
};
Is there any elegant solution to this case or it is just as it should be?
I see you have not yet captured the essence of the async nature of node.js ... for example you have a comment prior to department.save which says : and finally ... well the earlier logic may very will be still executing at that time ... also I strongly suggest you avoid your callback approach and learn how to do this using promises

Mongoose return populated array after save

I am trying to return an updated object as JSON, where the update was to set an array of objectIDs. I want the returned objected to have that array populated. For example, I have the following (simplified) model:
var UserSchema = new mongoose.Schema({
username: {type: String, unique: true, required: true},
friends: [{type: mongoose.Schema.Types.ObjectId, ref: 'User'}]
});
In my controller, I have:
exports.saveFriends = function(req, res) {
User.findById(req.params.user_id, function(err, user) {
// req.body.friends is JSON list of objectIDs for other users
user.friends = req.body.friends
user.save(function(err) {
user.populate({path: 'friends'}, function(err, ticket) {
if (err) {
res.send(err);
} else {
res.json(user);
}
});
});
});
}
This does in fact save the array properly as ObjectIDs, but the response user always shows "[]" as the array of friends.
Anyone see my issue?

Resources