Adding Properties in a forEach Loop in Mongoose - javascript

lets say each post in my posts array has two properties name and number. so its something like
var posts = [{name:"hey", number: 1}, {name:"foo", number:"2"}]
Javascript allows me to change these properties in foreach loop like this:
posts.forEach(function(post){
post.name = "bar"
});
and my array becomes:
var posts = [{name:"bar", number: 1}, {name:"bar", number:"2"}]
but it doesnt allow me add a new property like:
posts.forEach(function(post){
post.adress = "bar"
});
my object stays the same. Is there a way to add properties in a foreach loop in javascipt
edit:
this is happening using mongoose inside a callback..
Post.pagination(options, function(err, posts) {
if (err) console.log(err)
posts.forEach(function(post){
post.votetype = 1;
});
console.log(posts);
res.send({ posts : posts })
})
after this votetype property is not added

The problem is that data returned from Mongoose is immutable.
The code below is untested but should give you a hint on how to make the data mutable and modify it.
The key point is calling toObject() on the Mongoose document object you wish to modify.
Post.pagination(options, function(err, posts) {
if (err) console.log(err);
var resultPosts = posts.map(function(post) {
var tmpPost = post.toObject();
// Add properties...
tmpPost.votetype = 1;
return tmpPost;
});
console.log(resultPosts);
res.send({ posts : resultPosts });
});

Related

New Element Not Adding to JavaScript Object [duplicate]

lets say each post in my posts array has two properties name and number. so its something like
var posts = [{name:"hey", number: 1}, {name:"foo", number:"2"}]
Javascript allows me to change these properties in foreach loop like this:
posts.forEach(function(post){
post.name = "bar"
});
and my array becomes:
var posts = [{name:"bar", number: 1}, {name:"bar", number:"2"}]
but it doesnt allow me add a new property like:
posts.forEach(function(post){
post.adress = "bar"
});
my object stays the same. Is there a way to add properties in a foreach loop in javascipt
edit:
this is happening using mongoose inside a callback..
Post.pagination(options, function(err, posts) {
if (err) console.log(err)
posts.forEach(function(post){
post.votetype = 1;
});
console.log(posts);
res.send({ posts : posts })
})
after this votetype property is not added
The problem is that data returned from Mongoose is immutable.
The code below is untested but should give you a hint on how to make the data mutable and modify it.
The key point is calling toObject() on the Mongoose document object you wish to modify.
Post.pagination(options, function(err, posts) {
if (err) console.log(err);
var resultPosts = posts.map(function(post) {
var tmpPost = post.toObject();
// Add properties...
tmpPost.votetype = 1;
return tmpPost;
});
console.log(resultPosts);
res.send({ posts : resultPosts });
});

MongoDB - Mongoose $inc an object inside of an array of a document

So I am trying to update the value of an object inside an array inside a document. The Schema for the document looks like this:
var pollSchema = new Schema({
title: String,
hash: String,
answers: [{answer: String, votes: Number}]
});
And this is how my current update code looks like:
module.exports.updatePoll = function(socket, data){
var answerss = 'answers.0.votes';
hash = data.url.match(/\/([^/]*)$/)[1];
Poll.findOne({'hash' : hash}, function(err, poll){
if (err) return handleError(err);
console.log(poll.answers[data.id])
});
Poll.update({hash: hash}, {$inc: {'answers.{data.id}.votes': 1}}, function(err, poll){
if (err) return console.log(err);
//socket.emit('updatePoll', hash);
});
Ive tried pasting answerss in it instead of 'answers.{data.id}.votes'and also tried some other things. It only really works when I directly paste in 'answers.0.votes'. And this is a problem because the data.id can be a value of 0 to 10. I have no idea how I would implement this and the other answers on stackoverflow or google did not give much insight on the problem.
I hope you can help me. Thanks!
You don't need the initial find from what I can tell. You can do it all in one update statement. You need to match the array element you want to update simply by specifying its index:
var answerKey = 'answers.' + data.id + '.votes';
var updateJSON = {$inc: {}};
updateJSON.$inc[answerKey] = 1;
hash = data.url.match(/\/([^/]*)$/)[1];
Poll.update({hash: hash}, updateJSON, function(err, poll){
if (err) return console.log(err);
//socket.emit('updatePoll', hash);
});

Waterline Many-To-Many update with existing records

I am trying to write an update of my model to allow my application to pass an array of Id's (or an array of object Id's) to the server and for the update function to remove all existing relations and add in the new ones.
For example the data I am sending is:
{
"id":1,
"newIds":[3,4,5,6,7],
}
the current ids in the database are:
[1,2,3,4,5]
So after my update I would like the ids to be:
[3,4,5,6,7]
The update function I currently have is:
var id = req.param('id'),
newIds = req.param('newIds');
User.findOne({id: id})
.exec(function(err, results){
if(err) return res.serverError(err);
var user = results;
if (user){
if (newIds && newIds.length > 0) {
user.ids.add(newIds);
}
user.save(function(err) {
if(err) {
return res.serverError(err);
};
result.success = true;
return res.json(result);
});
}
});
However when I call the update I get an error complaining about the relationships already being there (It complains about id's 3,4 and 5 as they already exist).
Just replace your add() + save() with update().
var id = req.param('id'),
newIds = req.param('newIds');
User.update({id: id}, {ids: newIds})
.then(function(updatedUser) {
result.success = true; // assuming you've defined result somewhere earlier
return res.json(result)
})
.catch(res.serverError)
See: http://sailsjs.org/documentation/reference/waterline-orm/models/update
An array of primary key values passed to .update() for a collection association will set the association to contain only the records with those primary key values provided. That is- it unlinks all other records from the association.

Get json array from sub mongoose query

I have a sub query in mongoose need to get array out of sub query and attach to main json out put/ object.
my first query get user info which contains blocked_users array which is nothing but array of user id's.
i my second query we get profile details of blocker_users array and append to main user object in blocked_users.
var userId = ObjectID(req.body.user_id);
//Get user
newUserModel.findById(userId, function(err, user){
if(err){
utils.getResponse(res, req.url, messages.failure, "");
} else {
var userInfo = {};
var blcked_contacts;
//get users details from blocked contacts userid's array
newUserModel.find({'_id': {$in:user.blocked_contacts}}, function (err,blocked_users) {
if(blocked_users){
//blcked_contacts.push(blocked_users);
console.log(blocked_users);
return;
};
/*else{
blcked_contacts = [];
}*/
});
userInfo['blocked_contacts'].push(blocked_users);
userInfo['user_id'] = user.id;
userInfo['country_code'] = user.country_code;
//userInfo['blocked_contacts'].push(blcked_contacts);
//userInfo['blocked_contacts'] = user.blocked_contacts;
var userData = Array();
}
});
Don't really know what you're looking for. But saw a problem in your code. You've assigned the blocked_users to the blocked_contacts field outside the find method.
Since these calls are asynchronous in nature, it might happen that the assignment takes place even before the documents are fetched from MongoDB. So you should write your assignment statements inside the find methods' callback, just the way Medet did.
Noticed few mistakes in your code like trying to use .push on an object. You cant do
userInfo['blocked_contacts'].push(blocked_users); // incorrect as userInfo is an empty object and you dont have an array defined for userInfo['blocked_contacts']
You probably get cannot push into undefined error for this. So instead do
userInfo['blocked_contacts'] = blocked_users;
Also you have to do this inside the second find() as blocked_users is only available inside it. So your final query should be something like
var userId = ObjectID(req.body.user_id);
//Get user
newUserModel.findById(userId, function(err, user){
if(err){
utils.getResponse(res, req.url, messages.failure, "");
} else {
var userInfo = {};
//get users details from blocked contacts userid's array
newUserModel.find({'_id': {$in:user.blocked_contacts}}, function (err,blocked_users) {
if(blocked_users){
userInfo['user_id'] = user.id;
userInfo['country_code'] = user.country_code;
userInfo['blocked_contacts'] = blocked_users; // assign blocked_users into userInfo
console.log(userInfo) // Your required object
} else {
userInfo['user_id'] = user.id;
userInfo['country_code'] = user.country_code;
userInfo['blocked_contacts'] = []; // assign null array if no blocked users fould
}
});
var userData = Array();
}
});
The result of console.log should be an object like this
{
user_id : "..id of searched user...",
country_code : "..country code of searched user..",
blocked_contacts : [<array containing detais of all blocked users>] // null array if no users found
}

How to use "q" module for refactoring mongoose code?

I'm using mongoose to insert some data into mongodb. The code looks like:
var mongoose = require('mongoose');
mongoose.connect('mongo://localhost/test');
var conn = mongoose.connection;
// insert users
conn.collection('users').insert([{/*user1*/},{/*user2*/}], function(err, docs) {
var user1 = docs[0], user2 = docs[1];
// insert channels
conn.collection('channels').insert([{userId:user1._id},{userId:user2._id}], function(err, docs) {
var channel1 = docs[0], channel2 = docs[1];
// insert articles
conn.collection('articles').insert([{userId:user1._id,channelId:channel1._id},{}], function(err, docs) {
var article1 = docs[0], article2 = docs[1];
}
});
};
You can see there are a lot of nested callbacks there, so I'm trying to use q to refactor it.
I hope the code will look like:
Q.fcall(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
// Do something with value4
}, function (error) {
// Handle any error from step1 through step4
})
.end();
But I don't know how to do it.
You'll want to use Q.nfcall, documented in the README and the Wiki. All Mongoose methods are Node-style. I'll also use .spread instead of manually destructuring .then.
var mongoose = require('mongoose');
mongoose.connect('mongo://localhost/test');
var conn = mongoose.connection;
var users = conn.collection('users');
var channels = conn.collection('channels');
var articles = conn.collection('articles');
function getInsertedArticles() {
return Q.nfcall(users.insert.bind(users), [{/*user1*/},{/*user2*/}]).spread(function (user1, user2) {
return Q.nfcall(channels.insert.bind(channels), [{userId:user1._id},{userId:user2._id}]).spread(function (channel1, channel2) {
return Q.nfcall(articles.insert.bind(articles), [{userId:user1._id,channelId:channel1._id},{}]);
});
})
}
getInsertedArticles()
.spread(function (article1, article2) {
// you only get here if all three of the above steps succeeded
})
.fail(function (error) {
// you get here if any of the above three steps failed
}
);
In practice, you will rarely want to use .spread, since you usually are inserting an array that you don't know the size of. In that case the code can look more like this (here I also illustrate Q.nbind).
To compare with the original one is not quite fair, because your original has no error handling. A corrected Node-style version of the original would be like so:
var mongoose = require('mongoose');
mongoose.connect('mongo://localhost/test');
var conn = mongoose.connection;
function getInsertedArticles(cb) {
// insert users
conn.collection('users').insert([{/*user1*/},{/*user2*/}], function(err, docs) {
if (err) {
cb(err);
return;
}
var user1 = docs[0], user2 = docs[1];
// insert channels
conn.collection('channels').insert([{userId:user1._id},{userId:user2._id}], function(err, docs) {
if (err) {
cb(err);
return;
}
var channel1 = docs[0], channel2 = docs[1];
// insert articles
conn.collection('articles').insert([{userId:user1._id,channelId:channel1._id},{}], function(err, docs) {
if (err) {
cb(err);
return;
}
var article1 = docs[0], article2 = docs[1];
cb(null, [article1, article2]);
}
});
};
}
getInsertedArticles(function (err, articles) {
if (err) {
// you get here if any of the three steps failed.
// `articles` is `undefined`.
} else {
// you get here if all three succeeded.
// `err` is null.
}
});
With alternative deferred promise implementation, you may do it as following:
var mongoose = require('mongoose');
mongoose.connect('mongo://localhost/test');
var conn = mongoose.connection;
// Setup 'pinsert', promise version of 'insert' method
var promisify = require('deferred').promisify
mongoose.Collection.prototype.pinsert = promisify(mongoose.Collection.prototype.insert);
var user1, user2;
// insert users
conn.collection('users').pinsert([{/*user1*/},{/*user2*/}])
// insert channels
.then(function (users) {
user1 = users[0]; user2 = users[1];
return conn.collection('channels').pinsert([{userId:user1._id},{userId:user2._id}]);
})
// insert articles
.match(function (channel1, channel2) {
return conn.collection('articles').pinsert([{userId:user1._id,channelId:channel1._id},{}]);
})
.done(function (articles) {
// Do something with articles
}, function (err) {
// Handle any error that might have occurred on the way
});
Considering Model.save instead of Collection.insert (quite the same in our case).
You don't need to use Q, you can wrap yourself the save method and return directly a Mongoose Promise.
First create an utility method to wrap the save function, that's not very clean but something like:
//Utility function (put it in a better place)
var saveInPromise = function (model) {
var promise = new mongoose.Promise();
model.save(function (err, result) {
promise.resolve(err, result);
});
return promise;
}
Then you can use it instead of save to chain your promises
var User = mongoose.model('User');
var Channel = mongoose.model('Channel');
var Article = mongoose.model('Article');
//Step 1
var user = new User({data: 'value'});
saveInPromise(user).then(function () {
//Step 2
var channel = new Channel({user: user.id})
return saveInPromise(channel);
}).then(function (channel) {
//Step 3
var article = new Article({channel: channel.id})
return saveInPromise(article);
}, function (err) {
//A single place to handle your errors
});
I guess that's the kind of simplicity we are looking for.. right? Of course the utility function can be implemented with better integration with Mongoose.
Let me know what you think about that.
By the way there is an issue about that exact problem in the Mongoose Github:
Add 'promise' return value to model save operation
I hope it's gonna be solved soon. I think it takes some times because they are thinking of switching from mpromise to Q: See here and then here.
Two years later, this question just popped up in my RSS client ...
Things have moved on somewhat since May 2012 and we might choose to solve this one in a different way now. More specifically, the Javascript community has become "reduce-aware" since the decision to include Array.prototype.reduce (and other Array methods) in ECMAScript5. Array.prototype.reduce was always (and still is) available as a polyfill but was little appreciated by many of us at that time. Those who were running ahead of the curve may demur on this point, of course.
The problem posed in the question appears to be formulaic, with rules as follows :
The objects in the array passed as the first param to conn.collection(table).insert() build as follows (where N corresponds to the object's index in an array):
[ {}, ... ]
[ {userId:userN._id}, ... ]
[ {userId:userN._id, channelId:channelN._id}, ... ]
table names (in order) are : users, channels, articles.
the corresopnding object properties are : user, channel, article (ie the table names without the pluralizing 's').
A general pattern from this article by Taoofcode) for making asynchronous call in series is :
function workMyCollection(arr) {
return arr.reduce(function(promise, item) {
return promise.then(function(result) {
return doSomethingAsyncWithResult(item, result);
});
}, q());
}
With quite light adaptation, this pattern can be made to orchestrate the required sequencing :
function cascadeInsert(tables, n) {
/*
/* tables: array of unpluralisd table names
/* n: number of users to insert.
/* returns promise of completion|error
*/
var ids = []; // this outer array is available to the inner functions (to be read and written to).
for(var i=0; i<n; i++) { ids.push({}); } //initialize the ids array with n plain objects.
return tables.reduce(function (promise, t) {
return promise.then(function (docs) {
for(var i=0; i<ids.length; i++) {
if(!docs[i]) throw (new Error(t + ": returned documents list does not match the request"));//or simply `continue;` to be error tolerant (if acceptable server-side).
ids[i][t+'Id'] = docs[i]._id; //progressively add properties to the `ids` objects
}
return insert(ids, t + 's');
});
}, Q());
}
Lastly, here's the promise-returning worker function, insert() :
function insert(ids, t) {
/*
/* ids: array of plain objects with properties as defined by the rules
/* t: table name.
/* returns promise of docs
*/
var dfrd = Q.defer();
conn.collection(t).insert(ids, function(err, docs) {
(err) ? dfrd.reject(err) : dfrd.resolve(docs);
});
return dfrd.promise;
}
Thus, you can specify as parameters passed to cascadeInsert, the actual table/property names and the number of users to insert.
cascadeInsert( ['user', 'channel', 'article'], 2 ).then(function () {
// you get here if everything was successful
}).catch(function (err) {
// you get here if anything failed
});
This works nicely because the tables in the question all have regular plurals (user => users, channel => channels). If any of them was irregular (eg stimulus => stimuli, child => children), then we would need to rethink - (and probably implement a lookup hash). In any case, the adaptation would be fairly trivial.
Today we have mongoose-q as well. A plugin to mongoose that gives you stuff like execQ and saveQ which return Q promises.

Categories