mongoose, how to correctly remove reference with middleware? - node.js

can someone help me with a mongoose operation? I'm currently building this voting system.
I have this Poll model as:
var Poll = new Schema({
title: {
type: String,
required: true
},
options: [{text:String, count: {type: Number, default: 0}}],
author: {
type: Schema.ObjectId,
ref: 'Account',
},
disabled: {
type:Boolean,
default: false,
},
date: {type: Date, defalut: Date.now},
});
and I have this Log model as:
var Log = new Schema({
ip: String,
voter: {
type: Schema.ObjectId,
ref: 'Account'
},
poll: {
type: Schema.ObjectId,
ref: 'Poll'
},
date: {type: Date, defalut: Date.now},
});
each time a user vote for something , log will create something like:
{ ip: '::1',
voter: 5824e7c3b6e659459818004f,
poll: 58264b48f767f2270452b5cb,
_id: 58264b4cf767f2270452b5ce }
now should a user delete one of his poll, say 58264b48f767f2270452b5cb , I would like to also remove all the log documents that has same poll id in it.
I read some other answer and came up a middleware with
Poll.pre('remove', function(next){
var err = new Error('something went wrong');
this.model('Log').remove({poll: this._id}, function(err){
if (err) throw err;
})
next(err);
});
but it's not working at all.
what should I do? Thanks.

At the current state Model.remove() calls don't use hooks, why? Because a document could not be present in memory at time of the call, so would be necessary to query mongo first and then delete the doc to make sure a hook would work properly.
There's a CR for adding this behavior but is not implemented, yet.
So the current way to do this is to use something like:
myDoc.remove();
An example, this won't work:
var myAccount = new Account({
name: "jim"
})
var myPoll = new Poll({
question: "You like stuff?"
})
var myLog = new Log({
voter: myAccount,
poll: myPoll
})
myAccount.save()
.then(myPoll.save())
.then(myLog.save())
.then(Poll.remove({
question: "You like stuff?"
}, function(err) {
console.log(err)
}))
This will work instead:
myAccount.save()
.then(myPoll.save())
.then(myLog.save())
.then(myPoll.remove(function(err) {
console.log(err)
}))

Related

Trying to populate parent object's array but having trouble doing so

So I have some users/posts/comments and I want to make sure that when I post a comment on a post, that user's comment array is updated to contain the comment they just made. I tried searching around and it seemed like the best way of doing this was to use mongoose's populate, but it doesn't seem to be working. I'm still a beginner with Mongoose so any help or direction would be appreciated. Thanks!
I tried something like this:
comment.save((err) => { //save the comment
if (err) return next(err);
res.status(200).json(comment);
});
User.find({username: username})
.exec((err, user) => {
user.comments.push(comment); // says comments is undefined, but should be []
user.comment_count++; // also, is there a way to set comment_count equal to the length of the comments array? Should I use .pre()?
user.save(() => {
if (err) return next(err);
});
});
This gives me a error like cannot push into users.comments (undefined).
Here are my Schemas:
const PostSchema = new Schema({
postedBy: {
type: String,
required: true
},
title: {
type: String,
required: true
},
content: {
type: String,
required: true
},
createdAt: {type: Date, default: Date.now},
comments: [{ type: Schema.Types.ObjectId, ref: 'CommentSchema' }],
likedBy: [User]
});
const CommentSchema = new Schema({
parentID: { type: String,
required: true,
},
postedBy: {
type: User,
required: true
},
content: {
type: String,
required: true
},
createdAt: {type: Date, default: Date.now},
editedAt: {type: Date, default: Date.now},
likedBy: [User],
});
const UserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
},
email: {
comments: {
type: [{ type: Schema.Types.ObjectId, ref: 'CommentSchema' }],
default: [],
},
comment_count: { // how do I set this equal to the length of the comments array?
type: Number,
default: 0,
},
posts: {
type: [{ type: Schema.Types.ObjectId, ref: 'PostSchema' }],
default: [],
},
post_count: { // how do I set this equal to the length of the posts array?
type: Number,
default: 0,
},
});
I also tried population:
const comment = new Comment({
parentID,
postedBy,
content,
});
comment.save((err) => { //save the comment
if (err) return next(err);
res.status(200).json(comment);
});
User.find({ username: username })
.populate('comments')
.exec((err, user) => {
if(err) next(err);
console.log(user); // returns undefined for some reason
// also not sure what to do in this function...
});
This gives me an error like "cannot set headers after they are sent to the client."
first read more about how promises and call back worked.
quick fix would be.
comment.save((err) => { //save the comment
if (err) return next(err);
User.find({username: username})
.exec((err1, user) => {
if (err1) return next(err1);
res.status(200).json(user);
});
});
"cannot set headers after they are sent to the client." error occurred as you already send response.
res.status(200).json(comment);

ReferenceError: critiques is not defined in Node/Express App

I'm trying to work on the "review" part of a review/rating website. We have a mongoose Schema which I'm pushing new reviews to.
This is the schema:
var WorkSchema = new mongoose.Schema({
title: String,
genre: String,
workType: String,
length: Number,
ageRange: String,
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
manuscriptText: String,
critiques: [
{
reviewerName: String,
critique: String,
date: {
type: Date,
default: Date.now
}
}
],
ratingNumber: [Number],
ratingSum: {
type: Number,
default: 0
},
date: {
type: Date,
default: Date.now
}
});
When a user submits a new review, this is the post route. It is pushing the critique to the array of critiques associated to the work (confirmed by searching in mongodb), but I keep getting a reference error that "critique" is not defined in the render. I need to re-render the work page so that the reviewer can see that their critique has been added and displays on the front end. Not sure why this is happening since 'critiques' is practically everywhere.
router.post('/:id', function(req, res) {
Work.findByIdAndUpdate(req.params.id,
{
$push:
{
critiques: {
reviewerName: req.user.username,
critique: req.body.critique
}
}
}, { new: true}).populate('works', 'critiques').exec(function(err, foundWork) {
if (err) {
console.log(err);
} else {
res.render('work',
{
user: foundWork,
title: foundWork.title,
critiques: critiques,
currentUser: req.user,
work: foundWork
}
);
}
});
});
while you are rendering all the values after update.
you are doing critiques: critiques,
while critiques is not defined as a variable.
now you have 2 options , first is you can just show the full critiques array. as it is coming from the database.[may be you can do some operations with that data if need.]
like critiques: foundWork.critiques.
or otherwise yoou can just show the data you are inserting at that time as
critiques : req.body.critiques
like below:
Take from DB as it is
res.render("work", {
user: foundWork,
title: foundWork.title,
critiques: foundWork.critiques,
currentUser: req.user,
work: foundWork
});
take from body pushing the current element
res.render("work", {
user: foundWork,
title: foundWork.title,
critiques: req.body.critiques,
currentUser: req.user,
work: foundWork
});

Need some clarification on mongoose/mongodb populate command

Hello so I am making a basic app with users and posts.
I followed the mongoose documentation on population (http://mongoosejs.com/docs/2.7.x/docs/populate.html) and setup my Schemas so that the users and be connected to posts
var userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
email: String,
created_at: Date,
updated_at: Date,
admin: Boolean,
posts: [{ type: mongoose.Schema.ObjectId, ref: 'Post' }]
});
var postSchema = new mongoose.Schema({
_user : [{ type: mongoose.Schema.ObjectId, ref: 'User' }],
audioFile: { type: String, required: true },
imageFile: { type: String },
title: { type: String, required: true },
artist: { type: String, required: true },
start: { type: String, required: true },
stop: { type: String, required: true },
genre: { type: String, required: true },
tags: [{ type: String }]
});
app.get('/', function (req, res){
Post.find({}, function(err, allPosts){
if(!err){
res.render('main.njk', {
posts : allPosts,
title : 'Title',
isLogged : req.session.isLogged,
user : req.session.user,
messages : req.flash('alert')
});
} else { return done(err); }
});
});
Thats all fine and gravy and I can run a foreach loop on allPosts to pull each one in my HTML, but when I try to think of how I am going to display all the posts with their respective users attached to each post I am unsure of how to connect the two since all the examples in the mongoose doc is just mainly for findOne.
I was thinking something like this
app.get('/', function (req, res){
Post.find({}, function(err, allPosts){
if(!err){
allPosts.populate('_user', ['username']);
allPosts.exec(function (err, users){
if(err) console.log(err);
console.log(users);
});
res.render('main.njk', {
posts : allPosts,
title : 'Spaurk.net',
isLogged : req.session.isLogged,
user : req.session.user,
messages : req.flash('alert')
});
} else { return done(err); }
});
});
but that doesn't work of course.
So I was wondering if anyone with experience with this situation would be able to help me solve this.
Thanks a lot for any input.
EDIT, thanks to Daves help I was able to get the populate to work properly, I just cant pull the fields I want correctly with
Post.find({}).populate('_user').exec(function(err, allPosts){
In my loop {% for post in posts %}
, when I do post._user it shows the whole user schema, but when I do post._user.username it doesn't return anything. I am unsure as to why this is.
The proper way to structure a populate on a query is like this:
Post.find({})
.populate('_user')
.exec((err, allposts){...})
Then you will have an array of your Posts with the _user array populated. If you need to access a property of a user, you will need to do another loop through the _user array or specify with use you want to use _user[0].<property>

Mongoose - Better solution with appending additional information

I have two Schemas:
var ProgramSchema = new Schema({
active: Boolean,
name: String,
...
});
var UserSchema = new Schema({
username: String,
email: { type: String, lowercase: true },
...
partnerships: [{
program: { type: Schema.Types.ObjectId, ref: 'Program' },
status: { type: Number, default: 0 },
log: [{
status: { type: Number },
time: { type: Date, default: Date.now() },
comment: { type: String },
user: { type: Schema.Types.ObjectId, ref: 'User' }
}]
}]
});
Now I want to get all Program docs, but also append 'status' to each doc, to return if the program is already in a partnership with the logged in user.
My solution looks like this:
Program.find({active: true}, 'name owner image user.payments', function (err, p) {
if(err) { return handleError(res, err); }
})
.sort({_id: -1})
.exec(function(err, programs){
if(err) { return handleError(res, err); }
programs = _.map(programs, function(program){
var partner = _.find(req.user.partnerships, { program: program._id });
var status = 0;
if(partner){
status = partner.status;
}
program['partnership'] = status;
return program;
});
res.json(200, programs);
});
The req.user object contains all information about the logged in user, including the partnerships array.
To get this solution to work, I have to append
partnership: Schema.Types.Mixed
to the ProgramSchema.
This looks a bit messy and thats why I am asking for help. What do you think?
When you want to freely modify the result of a Mongoose query, add lean() to the query chain so that the docs (programs in this case) are plain JavaScript objects instead of Mongoose doc instances.
Program.find({active: true}, 'name owner image user.payments')
.lean() // <= Here
.sort({_id: -1})
.exec(function(err, programs){ ...
Then you can remove partnership from your schema definition. Your query will also execute faster.

modeling node.js with mongoose

I'm pretty new to Node.js and I'm starting a new web app just to study.
So, I'm using Mongoose.js and this is my model:
The party schema:
var PartySchema = new Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true
},
createdBy: {
type: Schema.ObjectId,
ref: 'User'
},
invitations: [{
type: Schema.ObjectId,
ref: 'Invitation'
}]
});
The Invitation schema:
var InvitationSchema = new Schema({
party: {
type: Schema.ObjectId,
ref: 'Party'
}
});
Ok,
So I wrote a test case like this, assume the other variable (party) is correctly initialized:
invitation = new Invitation({
party: party,
content: 'come to this awesome party'
});
When I pass this variable invitation to a method like this:
return Party.sendInvitation(invitation, function(err, invitation) {
should.exist(invitation);
should.not.exist(err);
done();
});
Accessing invitation.party inside the sendInvitation method is like this:
invitation.party[525439f929cf32be02000003]
As seem, I can't navigate through invitation.party.
How can I achieve this invitation.party.title?
I appreciate any help!
For test case, I think you can use data from query invitation with populate data from party.
Invitation
.findById(id)
.populate('title')
.exec(function (err, invitation) {
if (!err)
return invitation; //this invitation data must be contained party with title. invitation.party.title
})

Resources