How to know if Mongoose's upsert created a new document? - javascript

I have this code in node.js/express.js:
var User = mongoose.model('User');
var usersRouter = express.Router();
usersRouter.put('/:id', function(req, res) {
req.body._id = req.params.id;
var usr = new User(req.body);
usr.validate(function (err) {
if (err) {
res.status(400).json({});
return;
}
var upsertData = usr.toObject();
delete upsertData._id;
User.update({_id: usr._id}, upsertData, {upsert: true}, function(err) {
if (err) {
res.status(500).json({});
return;
}
res.status(204).json({});
});
});
});
It works fine, but I would like to send a different response to the client if a new document has been created (status 201 with json in response body) or an existing one has been updated (status 204).
Is there a way to tell the difference from the callback of User.update?

Use the third parameter from callback function:
...
User.update({_id: usr._id}, upsertData, {upsert: true}, function(err, num, n) {
if (err) {
res.status(500).json({});
return;
}
if (!n.updatedExisting) {
/* new document */
}
res.status(204).json({});
});
...
n is an object like this:
{ updatedExisting: false,
upserted: <ObjectId>,
n: 1,
connectionId: 11,
err: null,
ok: 1 }
updatedExisting property is true when a document was updated -- so it was created before. If it's false, then it means that the new document was created during this call.

Related

array push results in empty array javascript

I am trying to store API results into an array.
The data is displayed in console, but on pushing the data into an array, the array is still empty.
Here's the code:
app.post('/fetchFavoriteTweets/', verifyToken, function(req, res) {
var favorites = [];
dbConn.then( function (database) {
var dbo = database.db("twitter_search");
dbo.collection('users').findOne(
{ _id: ObjectId(req.userId) }, function(err, result) {
if(err) throw err;
if(!result.hasOwnProperty('favorite_tweets')) {
res.status(404).json({msg:'record not found'});
}
else {
result.favorite_tweets.forEach(function (tweet) {
T.get('statuses/show', {id: tweet.id}, function(err, data, response) {
if(!err){
favorites.push(data);
console.log(data); //this returns data
} else {
console.log(err);
}
});
});
console.log(favorites);
// res.status(200).json({msg:'success', data:favorites});
}
});
}).catch(function(e){console.log(e)})
});
It looks like you're defining the favorites array within the scope of the function callback. Try putting var favorites = []; above you app.post() call instead.
Also, keep in mind that it will only have a value after the callback is complete, so any synchronous code later down the line will only see the empty array value.
I've updated your code to get favorites by storing separately the promise and call it afterwards:
UPDATE
As you can see in the demo, i have 2x console.log at the bottom, the first one(C1) is contained in the promise favoritesPromise () and the second (C2) is after the promise.
Synchronous actions will never wait for asynchronus actions to take place, therefore in my example C2 will always be outputted before C1, even if console.log(1 ... ) is before console.log(2 ... ), they'll appear reversed in the console.
In the promise i added a setTimeout of 1ms to mock a request, it was all it took to achieve the current output. Another thing you can test is removing the setTimeout then output will change a bit, your promise becomes synchronus until it reaches resolve(favorites), that means favorites has all the data by now, but when resolve takes place, it becomes async, and in your console you will still see C2 first (but now with data) and C1 second.
In my earlier answer i tried to implement this reasoning to your code.
Keep it async folks!
var favorites = [];
var favoritesPromise = () => {
return new Promise((resolve, reject) => {
console.log('Retrieving data from the internet.');
// This timeout mocks your request to anything that is async or promie
setTimeout(() => {
console.log('Request done')
let resultFavorite_tweets = [{
id: 1,
name: 'a dog'
}, {
id: 2,
name: 'a cat'
}];
resultFavorite_tweets.forEach(item => {
favorites.push(item.name);
})
resolve(favorites);
// if you have an error use
// reject(err)
}, 1);
});
}
favoritesPromise().then(favList => {
console.log(1, 'this will always contain data from the internet, but will always be last', favList);
})
console.log(2, 'this will be empty (unless you remove setTimeout), but will always be first', favorites);
app.post('/fetchFavoriteTweets/', verifyToken, function(req, res) {
const favoritesPromise = () => {
return new Promise((resolve, reject) => {
var favorites = [];
dbConn.then(function(database) {
var dbo = database.db("twitter_search");
dbo.collection('users').findOne({
_id: ObjectId(req.userId)
}, function(err, result) {
if (err) reject(err);
if (!result.hasOwnProperty('favorite_tweets')) {
res.status(404).json({
msg: 'record not found'
});
} else {
result.favorite_tweets.forEach(function(tweet) {
T.get('statuses/show', {
id: tweet.id
}, function(err, data, response) {
if (!err) {
favorites.push(data);
console.log(data); //this returns data
} else {
console.log(err);
reject(err);
}
});
resolve(data);
});
console.log(favorites);
// res.status(200).json({msg:'success', data:favorites});
}
});
}).catch(function(e) {
reject(e)
})
});
}
// Here you call the promise to retrieve "favorites"
favoritesPromise().then(favoritesList => {
console.log('your favorites array', favoritesList)
})
})
Try next code
app.post('/fetchFavoriteTweets/', verifyToken, function (req, res) {
var favorites = [];
dbConn.then(function (database) {
var dbo = database.db("twitter_search");
dbo.collection('users').findOne(
{ _id: ObjectId(req.userId) }, function (err, result) {
if (err) throw err;
if (!result.hasOwnProperty('favorite_tweets')) {
res.status(404).json({ msg: 'record not found' });
}
else {
// Counter
let count = result.favorite_tweets.length;
result.favorite_tweets.forEach(function (tweet) {
T.get('statuses/show', { id: tweet.id }, function (err, data, response) {
// Decrease count
count -= 1;
if (!err) {
favorites.push(data);
// Check if count is zero
if (count === 0) {
console.log(favorites);
res.status(200).json({msg:'success', data:favorites});
}
} else {
console.log(err);
}
});
});
}
});
}).catch(function (e) { console.log(e) })
});

node js app using a lot of memory

I built an API for my mobile apps using NodeJs. I built this app to add contacts to my Mongo DB, but the app uses too much memory.
var ObjectID = require('mongodb').ObjectID
module.exports = function(app, db) {
const myDb = db.db('sycontacts')
app.post('/addcontacts', (req, res) => {
const jason = JSON.parse(req.body.body);
jason.forEach(function(value){
const contact = { n: value.n, p: value.p };
const details = { p: value.p };
var bool = true;
myDb.collection('contacts').find(details).toArray(function(err, item) {
if (err) {
res.send({ 'error': 'An error has occured' });
} else {
if(item.length>0){
item.forEach(function(value){
if(value.n == contact.n){
bool= false;
return;
}
if(!bool)return;
});
if(bool){
myDb.collection('contacts').insertOne(contact, (err) => {
if (err) {
res.send({ 'error': 'An error has occured' });
}
});
}
}else {
myDb.collection('contacts').insertOne(contact, (err) => {
if (err) {
res.send({ 'error': 'An error has occured' });
}
});
}
}
});
});
res.send('findEd');
});
};
My database has about 5000000 documents.. Can anyone help me to make this app perform better and reduce its memory usage?
There's some error on your code and some missing optimization :
You do async function inside forEach function, to loop wisth async function use async.eachOf or async.eachOfLimit
You retrieve all your document in your database to insert missing documents
Do something like this instead :
var async = require("async");
const jason = JSON.parse(req.body.body);
// process 10 document in same time at maximum
async.eachOfLimit(jason, 10, function(value, index, callback){
// upsert document, update all or insert if match not found
myDb.collection('contacts').update({ n: value.n, p: value.p }, {n: value.n, p: value.p}, {multi: false, upsert: true}, function(err){
return callback(err);
});
}, function(err, result){
// all is finished
if(err)
{
return res.status(500).json({err: err});
}
else
{
return res.status(200).json({err: null});
}
})

How to properly use Mongoose query, Find and update object array

Here is Main Code to look into
function update(feed){
//.. do set up
var target = feed.title;
var value = {index: feed.value};
var query = { test: target, 'array.index': {$ne: value.index} },
update = { $push : {"array" : value} },
options = { upsert: true, new: true };
Model.findOneAndUpdate(query, update, options, function(err, docs) {
if(err) throw err;
console.log(docs);
}
);}
(I modified the code to be general. if you need specific code, just ask me to update this post)
I am trying to do Model.findOneandUpdate with upsert: true.
My code executes when specific event emitted
feedparser.on('readable', function() {
update(this.read());
});
Becasue of 'array.index': {$ne: value.index} query, the code creates new one after the first execution.
Therefore,
db.collection.find()
returns multiple documents that has same property with different ObjectId.
For Example,
{"_id":ObjectId("1"), test: "A", array:[{index:"1"}]}
{"_id":ObjectId("2"), test: "A", array:[{index:"1"}]}
I want to be the code do
Check document exist or not.
if Exist, add new value to the Document's array. Also, new value should be unique by index.
if NOT Exist, create new Document and add new value to new Document's array.
UPDATE:
I also try to do it by
var doc = Model.findOne({ test: target });
if(doc != null){
var query = { _id: doc._id, 'array.index': {$ne: value.index} };
var update = { $push : {"array" : value} };
var options = {new: true};
Model.update(query, update, options, function(err, d){
if(err) throw err;
console.log(d);
});
}else{
doc = {
test: target,
array:[value]
};
Model.create(doc, function (err, res){
if(err) throw err;
console.log(res);
});
}
This code result in do Nothing.
UPDATE
I also try this by
Model.findOne({ test:target }, function(err, doc){
if(doc === null){
doc = {
test: target
arrays:[value]
};
Animation.create(doc, {new: true}, function (err, res){
if(err) throw err;
console.log(res);
});
}else{
var query = { _id: doc._id, 'arrays.index': {$ne: value.index} };
var update = { $push : {"arrays" : value} };
var options = {new: true};
Animation.update(query, update, options, function(err, res){
if(err) throw err;
console.log(res);
});
}
});
But, it create new Document by each different index value.
UPDATE
var query = { test: target, 'array:index': {$ne: value.index} };
var update = { $push : {'array' : value}};
var options = {new: true};
Model.findOneAndUpdate(query, update, options, function(err, doc){
if(err) throw err;
if(doc === null){
doc = new Model({
test: target,
array:[value]
});
doc.save();
}
});
It also does not work...
I found how to solve this problem!
First, the reason why the problem arise is I am trying to do upsert with $ne.
Here is my code,
Animation.findOne({ title: AnimationTitle, team: TeamName }, function(err, result){
if(err) { throw err; }
if(result){
Animation.findOneAndUpdate({ title: AnimationTitle, team: TeamName, 'episodes.number': {$ne: episode.number} }, { $addToSet : {'episodes' : episode}}, {new: true}, function(err, result){
if(err) { throw err; }
});
}else{
Animation.findOneAndUpdate({ title: AnimationTitle, team: TeamName }, { $addToSet : {'episodes' : episode}}, {new: true, upsert: true}, function(err, result){
if(err) { throw err; }
});
}
});

Find() function retunrs undefined - Mongoose and Node.js

I was trying to do a simple function with Node.js and Mongoose that returns true if the model is empty.
The mongoose configuration is fine:
var mongoose = require('mongoose');
var db = mongoose.createConnection( 'mongodb://localhost:27017/prueba' );
var userSchema = mongoose.Schema({
phoneNumber: Number,
name: String
});
var User = db.model('User', userSchema, 'User'');
Then I tried to do this:
User.find(function(err, data) {
if (err) {console.log(err)};
console.log(data.length == 0 );
});
And it works fine, it logs true, or false.
Then I tried to do:
var isUsersEmpty = function () {
User.find(function(err, data) {
if (err) {console.log(err)};
console.log(data.length == 0);
});
}
isUsersEmpty();
And again it works fine, it logs true or false, buy if I do:
var isUsersEmpty2 = function () {
User.find(function(err, data) {
if (err) {console.log(err)};
return data.length == 1;
});
}
console.log(isUsersEmpty2());
Then the log prints "Undefined".
What can I do if I need a function that returns true or false to do things like this:
if (isUsersEmpty2()) {} //Do something here...
And isUsersEmpty2() returns always undefined.
isUsersEmpty2() returns a promise , which means you can't just log it like you did. You need to send a response from the function. This should work:
var isUsersEmpty2 = function (res) {
User.find(function(err, data) {
if (err) res(err, null);
res(null, data.length == 1);
});
}
isUsersEmpty2(function(err, res) {
if(res) {/*do something*/}
});

How to spy on a class method in node-jasmine?

I have a module User - like this:
module.exports = User = (function() {
function User(params) {
this.id = params.id;
this.first_name = params.first_name || '';
this.last_name = params.last_name || '';
this.username = params.username;
this.email = params.email;
this.password = params.password;
};
User.findByUsername = function(username, callback) {
if (!_.isEmpty(username)) {
var opts = {table: TABLE, where: {sql: "username='"+username+"'"}};
QueryDispatcher.findWhere(opts, function(err, result) {
if(!_.isEmpty(err)) { return callback(err, null)}
callback(null, result.rows[0]);
});
};
};
return User;
};
The function that uses the class method:
module.exports = AuthStrategies = (function() {
AuthStrategies.localStrategy = function(username, password, done) {
async.waterfall([
function(callback) {
User.findByUsername(username, function(err, user){
if (err) { callback(err) };
if (_.isEmpty(user)) {
callback(null, false, { message: 'Incorrect username.' });
};
callback(null, user, null)
});
},
function(user, opts, callback) {
"do something here and call the next callback"
}]
, function(err, user, opts) {
if(err) { return done(err)}
if(!user) { return done(null, false, opts.message)}
done(null, user)
});
};
return AuthStrategies;
})();
I have my jasmine test -
var Auth = require('path to AuthStrategies module')
describe('Auth', function() {
describe('#AuthStrategies.localStrategy', function() {
describe('when user creds are valid', function() {
var test_user;
beforeEach(function(){
test_user = new User({
username: 'test996'
, password: 'password123'
, email: 'testemamil#email.com'
, first_name: ''
, last_name: ''
});
spyOn(User, "findByUsername").and.callFake(function(usrename, cb) {
cb(null, test_user);
});
});
it('returns user object', function(done) {
Auth.localStrategy('test996', 'password123', function(err, user, opts) {
expect(err).toEqual(null);
expect(user).toEqual(test_user);
done()
})
});
});
});
});
Essentially I want to stub out the User Class method findByUsername and fake the callback with my own results i.e nul error and a user(as if the find was successfully).
I have Spy on many "class" methods in my app and don't have this problem. This is baffling me a bit. The error only shows when I add .and.callThrough or .and.callFake to the spy.. the moment I remove this the test just times out ...which makes sense as the spy works and doesn't call the callback needed for the async waterfall to continue.
The error I am getting is -
So I figured it out -
The error you see above happens anyway. The reason I was getting the above "extra info" which was throwing me off btw - Was because I was running the test separately.
./node_modules/.bin/jasmine ./tests_to_run_spec.js
What would normal happen - is I would get a timeout failure due to a missing callback. as in my case above I wasn't calling the callback in the faked function I sup[plied properly.
actually even more interestingly - running jasmine-node from my PATH doesn't like the .and.... being called on this particular spy. Really have no idea. but that how I got the spyOn(User, 'findByUsername').and.callFake ... to work

Categories