How to remove embedded scheme document in mongoose? - node.js

Having a problem with like removal
My schema
/**
* Article Schema
*/
var Comments = new Schema({
body: { type : String, default : '' },
user: { type : Schema.ObjectId, ref : 'User' },
createdAt: { type : Date, default : Date.now }
})
var Likes = new Schema({
user: { type : Schema.ObjectId, ref : 'User' },
createdAt: { type : Date, default : Date.now }
})
var ArticleSchema = new Schema({
title: {type : String, default : '', trim : true},
body: {type : String, default : '', trim : true},
geo: {type: [Number], set: setTags},
user: {type : Schema.ObjectId, ref : 'User'},
comments: [Comments],
likes: [Likes],
tags: {type: [], get: getTags, set: setTags},
image: {type:String, default : ''},
createdAt : {type : Date, default : Date.now}
})
And I want to make like/unlike functionality, adding like works but removing simply doesnt change the document.
Controller:
exports.like = function (req, res) {
var article = req.article
var user = req.user
Article.getArticleByWithLikesByUserId(req.user.id, function(err, articles) {
console.log(articles);
if(!articles.length){
console.log('adding like',new Date);
article.addLike(user, function (err) {
if (err) return res.render('500')
res.redirect('/articles/'+ article.id)
});
}
else{
console.log('removing like',new Date);
article.removeLike(user, function (err) {
if (err) return res.render('500')
res.redirect('/articles/'+ article.id)
});
}
});
schema methods:
addLike: function (user, cb){
this.likes.push({
user: user._id
})
this.save(cb);
},
removeLike: function (user, cb){
var that = this;
this.likes.forEach(function(like, index) {
if(like.user == user.id){
var newLikesObj = that.likes.toObject();
console.log("before splice:\n ",newLikesObj);
newLikesObj.splice(index,1);
console.log("after splice: \n",newLikesObj);
that.likes = newLikesObj;
that.save(cb)
}
});
and from logs splice seems to be working ok
before splice:
[ { user: 5247095b5696e45c2b000102,
_id: 524defc87153550829000103,
createdAt: Fri Oct 04 2013 01:29:28 GMT+0300 (Финляндия (лето)) },
{ user: 5247095b5696e45c2b000002,
_id: 524df3c2a663050805000003,
createdAt: Fri Oct 04 2013 01:46:26 GMT+0300 (Финляндия (лето)) } ]
after splice:
[ { user: 5247095b5696e45c2b000102,
_id: 524defc87153550829000103,
createdAt: Fri Oct 04 2013 01:29:28 GMT+0300 (Финляндия (лето)) } ]
static method:
getArticleByWithLikesByUserId: function (id, cb) {
this.find({ 'likes.user' : id })
.populate('user', 'name email username')
.populate('comments.user')
.exec(cb)
}
I've also tried this update method, still doesnt remove like even though numberAffected is showing 1
this.update({$pull : {'likes.user':user.id}}, function(err, numberAffected){
if(!err){
return console.log('like removed');
} else {
return console.log(err);
}
});

I can't say what's going wrong with your example, other than that you're going about it the wrong way. MongoDB provides an an easier, built in way to remove items from an array either by specifying the exactly, or via a query:
Behold the $pull operator.
In mongoose one way to do this would be:
Article.findByIdAndUpdate(this._id, {
$pull: {
likes: {user: user._id}
}
}, cb);

Related

Is it possible to query subdocuments directly using mongoose?

let's say there was a User model and a Post model. In this situation User's would have many posts; User would be the parent and Post would be the child. Is it possible to query for posts directly?
For instance if I wanted to do something like
app.get('/post/search/:query', (req,res) => {
Posts.find({title: req.params.query }, (err,post) => {
res.send(JSON.stringify(post))
})
})
or would one have to do:
app.get('/post/search/:query',(req,res) => {
let resultsFromQuery = [];
User.find({'post.title':req.params.query'}, (err,user) => {
user.posts.forEach((post) => {
if(post.title === req.params.query){
resultsFromQuery.push(post);
}
})
})
res.send(JSON.stringify(resultsFromQuery))
})
EDIT: Here is my schema's.
User Schema (Parent)
const mongoose = require('mongoose'),
Schema = mongoose.Schema,
PostSchema = require('./post.js');
let UserSchema = new Schema({
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
posts: [PostSchema]
})
module.exports = mongoose.model('User',UserSchema);
Post Schema (Child)
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
let PostSchema = new Schema({
title: {
type: String
},
description: {
type: String
},
image: {
type: String
},
original_poster: {
id: {
type: String,
required: true
},
username: {
type: String,
required: true
}
},
tags: {
type: [String],
required: true
}
})
module.exports = PostSchema;
EDIT:
Here is a sample document
the result of db.users.find({username: 'john'})
{
"_id" : ObjectId("5a163317bf92864245250cf4"),
"username" : "john",
"password" : "$2a$10$mvE.UNgvBZgOURAv28xyA.UdlJi4Zj9IX.OIiOCdp/HC.Cpkuq.ru",
"posts" : [
{
"_id" : ObjectId("5a17c32d54d6ef4987ea275b"),
"title" : "Dogs are cool",
"description" : "I like huskies",
"image" : "https://media1.giphy.com/media/EvRj5lfd8ctUY/giphy.gif",
"original_poster" : {
"id" : "5a163317bf92864245250cf4",
"username" : "john"
},
"tags" : [
"puppies",
"dogs"
]
}
],
"__v" : 1
}
Yes you can find directly the post title from the user model. like bellow
User.find({"posts.title": "Cats are cool"}, (err, users) => {
if(err) {
// return error
}
return res.send(users)
})
That will return user with all post not only the matching post title. So to return only matching post title can use $ positional operator. like this query
User.find({"posts.title": "Cats are cool"},
{username: 1, "posts.$": 1}, // add that you need to project
(err, users) => {
if(err) {
// return error
}
return res.send(users)
})
that only return matching post
Since you are saving OP data, why not do:
// you'll need to adapt how your are getting the user-id here
const { user } = req
Post.find({ title: 'the title', 'original_poster.id': user.id }, (err, posts) => {
console.log(posts); })
Though I would advise you to adjust your Post-schema:
original_poster: {
type: Schema.Types.ObjectId,
ref: 'User'
}
},
Then you can do Post.find({}).populate('original_poster') to include it in your results.!

In Mogoose i found my document with find, but using update not

I'm using nodejs and mongo (with mongooose) to build a simple aplication, but i have this little problem.
If i search my documento using a find method, i found the document (and yes, i can update in this), but if i use a update method, o cant find it. Why?
My controller method
var query = {
_id: ObjectId(req.params.runner_id)
};
Runner.find(query, function(e,o) {
console.log(o); //here i found
})
Runner.update(query, req.body, function (err, qtd) {
console.log(qtd); //here note
if (err) {
...
} else {
...
}
})
My Schema
module.exports = mongoose.model("Runner", new Schema({
cellphone: {
type: String,
require: "qual o telefone?",
unique: true,
validate: {
validator: function (v) {
var re = /^\d{11}$/;
return (v == null || v.trim().length < 1) || re.test(v);
},
message: "telefone inválido"
}
},
created_at: {
type: Date,
default: Date.now
},
advisors: [{
name: {
type: String,
require: "entre com o nome"
},
email: {
type: String,
require: "entre com o e-mail"
},
advisor: {
type: ObjectId,
require: "qual a assessoria?"
}
}]
}));
My output
with update -> { ok: 0, n: 0, nModified: 0 }
with find -> [ { _id: 5a0b99a9328fec0e4111ca52,
cellphone: '85999981114',
__v: 0,
advisors: [ [Object] ],
created_at: Wed Nov 15 2017 01:34:33 GMT+0000 (UTC) } ]
app.get('/', function(req, res){
var query = {
_id: ObjectId(req.params.id)
};
Runner.find(query, function(e,o) {
console.log(o); //here i found
})
}
app.put('/:id', function(req, res){
var Id=req.params.id;
Runner.findById(Id, function (err, qtd) {
console.log(qtd); //here qtd will found
if (err) {
...
} else {
...
}
}

How to obtain object id and also how to update

I am trying to obtain the object id for any article already in db so that I can validate that the article exists before comments are made.
The issue is on the router (/blog/article/comment). I cannot get the article object id from /blog/article/:postid. I want to pass this id to articleId like this:
articleId: req.params.postid
I have also tried:
articleId: req.article._id
model structure: comment.js
var mongoose = require('mongoose');
var CommentSchema = new mongoose.Schema({
content: { type: String },
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
articleId: { type: mongoose.Schema.Types.ObjectId, ref:'Article' },
dateCommented: { type: Date, default : Date.now }
});
Article model: article.js
var ArticleSchema = new mongoose.Schema({
category: { type: mongoose.Schema.Types.ObjectId, ref: 'Category' },
commentId:{type: mongoose.Schema.Types.ObjectId, ref:'Comment'},
title: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User'},
blog: [{
topic: { type: String, unique: false, lowercase: true },
body: { type: String, unique: false, lowercase: true },
tags: [ 'first', 'mongodb', 'express'],
created: Date,
modified: { type : Date, default : Date.now },
state: { type: String, unique: false, lowercase: true }
}]
});
main.js
router.param('postid', function(req, res, next, id) {
if (id.length !=24) return next(new Error ('The post id is not having the correct length'));
//articleId: req.param('postid'),
Article.findOne({ _id: ObjectId(id)}, function(err, article) {
if (err) return next(new Error('Make sure you provided correct post id'));
req.article = article;
next();
});
});
router.get('/blog/article/:postid', function (req, res, next) {
Article.findById({ _id: req.params.postid }, function (err, article) {
if (err) return next(err);
res.render('main/publishedArticle', {
article: article
});
});
});
router.post('/blog/article/comment', function(req, res, next) {
async.waterfall([
function(callback) {
var comment = new Comment({
articleId: req.params.postid,
content: req.body.content,
user: req.user._id
});
comment.save(function(err) {
if (err) return next (err);
req.flash('success', 'Thank you for your comment');
callback(err, comment);
});
},
function(comment) {
Article.update({_id : comment.articleId }, { $set: { commentId: {} }}, function(err, updated) {
if (updated) {
res.redirect('/')
}
});
}
]);
});
Another issue I have is how to update the commentId for each comment in the Article
Article.update({_id : comment.articleId }, { $set: { commentId: {} }}, function(err, updated)
Since the /blog/article/comment route is a post request. Just submit your articleId in the body of that request. You'll have to send it up from the client. You can access it with req.body.articleID (If that is what you call the variable).
See here for more info on POST requests in node.
For your second question:
Within your article schema you have commentId, That is a single record. What you want is an array of comments. Something like this:
comments: [{type: mongoose.Schema.Types.ObjectId, ref:'Comment'}]
Then within your code...
...
function(comment) {
//comment should contain all the comments
//Grab the article
Article.findOne({ _id: comment.articleId}, function(err, article){
//Go through all the comments in 'comment' compare them with the ones in artcle.comments.
//The ones that aren't already in the article object get put into newComments...
var newComments = [];
Article.update({ _id: comment.articleId }, { $addToSet: { comments: newComments } }, function(err, updated) {
if (updated) {
res.redirect('/')
}
});
});
}
...
I didn't fully implement the code, but it should get you off to the right start.
addToSet Documentation
Some more examples of add to set

Mongoose Subdocument array pushing

My scenario is if person1 accepting person2 deal means..the person1_id will save inside that person2 particular deal field accepted,i have tried the code it was working perfectly if a accepted user(person2) has one deal but in case of more than one deal it was updating but deleting other deals (i.e,the suppose the person2 having 3 deals means if person1 accepting 3rd deal the accepted user id was updating in 3rd deal and the 1st and 2nd deal was deleted).Anyone please help me how to save only the updated deal array
var incomingUser = req.user;//accepting user accesstoken in header(person1)
if(req.params.id){
var id = req.params.id;//deal id
console.log("DealId:"+id + "Acceptinguser:"+incomingUser.name);
User.findOne(
{
"deals": {
$elemMatch: {
_id: id
}
}
},
function(err, data){
console.log("Dealer:" +data.name);
console.log("deal:"+ data.deals);
if(err){
console.log("User not found");
res.send(new restify.ResourceNotFoundError('failed','Deal not found'));
return next();
}
var dealObj = _.filter(data.deals, { id: id })[0];
console.log("Deal Obj" + dealObj);
var acceptingUser = incomingUser;
console.log("accepting user:" +acceptingUser._id);
dealObj.accepted = acceptingUser._id;
console.log("accept id: "+ dealObj.accepted);
data.deals = dealObj;
console.log("data:"+ data.deals);
data.save(function (err, result){
console.log("Result:" + result);
if(err){
console.log("Internal error");
res.send(new restifyc.InternalError('failed','Error accepting'));
return next();
}
console.log("saved");
res.send(200,{user: result});
return next();
});
});
}
}
And my schema is
var dealSchema = new mongoose.Schema({
shopName: {type: String,required: true},
deal: {type: String,required: true},
price:{type: Number,required: true},
start:{type: Date,default: Date.now},
end:{type: Date},
expiry:{type: Date},
comments:{type: String},
accepted: {type:mongoose.Schema.Types.ObjectId, ref:'user'},//person1 _id
rejected: {type:mongoose.Schema.Types.ObjectId, ref: 'user'}
});
var userSchema = new mongoose.Schema({
name: { type: String,required: true},
phone: { type: Number, required: true,unique: true},
email:{type: String},
password: {type: String},
deals:[dealSchema]
}, {collection: 'user'});
mongoose.model('Deal', dealSchema);
mongoose.model('user', userSchema);
Yep in order to update specifically what you need you can use the <array>.$ for the specified position of the element:
User.update(
"deals": {
$elemMatch: {
_id: id
}
}, {
"$set": {
"deals.$" : {/*your deal data*/}
}
}, function(err, doc) {
});
More details on how to use the $ wildcard https://docs.mongodb.org/manual/reference/operator/update/positional/

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.

Resources