Why is MongoDB ignoring some of my updates? - node.js

I've been building an application in Node.JS using the native MongoDB driver - it includes contacts, and when a user accepts a contact, it should remove from "pending" and "sent" contacts, then add to "contacts".
Example code and documents:
/*
=============================
User "john"
=============================
{
username: "john",
contacts: ["jim"],
pending_contacts: ["bob"]
}
=============================
User "bob"
=============================
{
username: "bob",
contacts: ["dave"],
sent_contacts: ["john"]
}
=============================
What SHOULD happen
=============================
{
username: "bob",
contacts: ["dave", "john"],
sent_contacts: []
},
{
username: "john",
contacts: ["jim", "bob"],
pending_contacts: []
}
=============================
What ACTUALLY happens
=============================
{
username: "john",
contacts: ["jim", "bob"],
pending_contacts: ["bob"]
},
{
username: "bob",
contacts: ["dave", "john"],
sent_contacts: ["john"]
}
*/
var col = this.db.collection('users');
var contact = "bob", username = "john";
var who = [contact, username];
var finishCount = 0;
// finish will run 3 times before callback
function finish(name) {
console.log(name, ' has finished');
finishCount++;
if(finishCount<3) return;
callback(false, null);
}
// run if there's an error
function failed(err) {
callback(err, null)
}
console.log('removing %s and %s from pending and sent', username, contact)
col.update(
{username: { $in: who }},
{
$pullAll: {
sent_contacts: who,
pending_contacts: who
}
}, {multi: 1},
function(err,data) {
if(err) return failed(err);
finish('REMOVE_CONTACTS');
}
);
col.update(
{username: username}, {$addToSet: {contacts: contact}},
function(err,res) {
if(err) return failed(err);
console.log('added 1');
finish('ADD_TO_USER');
}
);
col.update(
{username: contact}, {$addToSet: {contacts: username}},
function(err,res) {
if(err) return failed(err);
console.log('added 2');
finish('ADD_TO_CONTACT');
}
);
The first update removes the contact and the owner from each-others pending/sent list, the second and third update add the owner to the contact's contact list and vice versa.
The issue is, the final result appears to be as if the removal never happened, though the removal query works perfectly fine by itself. I don't know if this is a problem with MongoDB itself (or if it's intended), or if it's an issue with the driver, so I hope someone can at least clarify this for me.
NOTE: Yes I know they run asynchronously. Running them one after the other by putting each update in the previous callback does NOT make a difference. Before anyone complains about how awful this code looks, I previously had it set up within Async.JS but I removed it from this code sample to ensure that Asyn.cJS was not responsible for the issues.

Using the node native driver this works for me every time:
var mongodb = require('mongodb'),
async = require('async'),
MongoClient = mongodb.MongoClient;
var user = "john",
contact = "bob";
var contactsList = [
{
"username": "john",
"contacts": [
"jim"
],
"pending_contacts": [
"bob"
]
},
{
"username": "bob",
"contacts": [
"dave"
],
"sent_contacts": [
"john"
]
}
];
MongoClient.connect('mongodb://localhost/test',function(err,db) {
var coll = db.collection("contacts");
async.series(
[
// Wipe clean
function(callback) {
coll.remove({},callback)
},
// Init collection
function(callback) {
async.each(contactsList,function(contact,callback) {
coll.insert(contact,callback);
},callback);
},
// Do updates
function(callback) {
// Init batch
var bulk = coll.initializeOrderedBulkOp();
// Add to user and pull from pending
bulk.find({
"username": user,
"contacts": { "$ne": contact },
}).updateOne({
"$push": { "contacts": contact },
"$pull": { "pending_contacts": contact }
});
// Add to contact and pull from sent
bulk.find({
"username": contact,
"contacts": { "$ne": user },
"sent_contacts": user
}).updateOne({
"$push": { "contacts": user },
"$pull": { "sent_contacts": user }
});
// Execute
bulk.execute(function(err,response) {
console.log( response.toJSON() );
callback(err);
});
},
// List collection
function(callback) {
coll.find({}).toArray(function(err,results) {
console.log(results);
callback(err);
});
}
],
function(err) {
if (err) throw err;
db.close();
}
);
});
And the output:
{ ok: 1,
writeErrors: [],
writeConcernErrors: [],
insertedIds: [],
nInserted: 0,
nUpserted: 0,
nMatched: 2,
nModified: 2,
nRemoved: 0,
upserted: [] }
[ { _id: 55b0c16934fadce812cdcf9d,
username: 'john',
contacts: [ 'jim', 'bob' ],
pending_contacts: [] },
{ _id: 55b0c16934fadce812cdcf9e,
username: 'bob',
contacts: [ 'dave', 'john' ],
sent_contacts: [] } ]
Improvements here are basically to use the Bulk Operations API and send all updates at once to the server and get a single response. Also note the use of operators in the updates and the query selection as well.
Simply put, you already know the "user" as well as the "contact" they are accepting. The contact to be accepted is "pending" and the contact themselves have the user in "sent".
These are really just simple $push and $pull operations on either array as is appropriate. Rather than using $addToSet here, the query conditions make sure that the expected values are present when performing the update. This also preserves "order" which $addToSet can basically not guarantee, because it's a "set", which is un-ordered.
One send to the server and one callback response, leaving both users updated correctly. Makes more sense then sending multiple updates and waiting for the callback response from each.
Anyhow, this is a complete self contained listing with only the two named dependencies, so you can easily run it yourself and confirm the results.
When I say "Complete and self contained" it means start a new project and simply run the code. Here's the complete instruction:
mkdir sample
cd sample
npm init
npm install mongodb --save
npm install async --save
Then create a file with the code listing in that folder, say test.js and then run:
node test.js

Related

CouchDB count all documents and return a number?

We have a college project in CouchDB and I'm using node, I want to create a view that returns a number of all my documents by email.
I cannot find anything that works and I'm not sure what I'm missing, I tried a lot of different reduce functions and emit methods.
Thanks for any answers.
The documents have 2 fields, name and email
Do not use the db endpoint because the response field doc_count includes design documents along with other documents that may not have an email field.
A straight forward way to do this is with a view. The code snippet demonstrates the difference between db info doc_count and a view's total_rows using PouchDB. I'd guess there's probably more interesting uses for the index.
The design doc is trivial
{
_id: '_design/my_index',
views: {
email: {
map: function(doc) {
if (doc.email) emit(doc.email);
}.toString()
}
}
}
And the view query is very efficient and simple.
db.query('my_index/email', {
include_docs: false,
limit: 0
})
const gel = id => document.getElementById(id);
let db;
function setJsonToText(elId, json) {
gel(elId).innerText = JSON.stringify(json, undefined, 3);
}
async function view() {
// display db info
setJsonToText('info', await db.info());
// display total number or rows in the email index
const result = await db.query('my_index/email', {
include_docs: false,
limit: 0
});
setJsonToText('view', result);
}
// canned test documents
function getDocsToInstall() {
return [{
email: 'jerry#garcia.com',
},
{
email: 'bob#weir.com',
},
{
email: 'phil#lesh.com'
},
{
email: 'wavy#gravy.com'
},
{
email: 'samson#delilah.com'
},
{
email: 'cosmic#charlie.com'
},
// design doc
{
_id: '_design/my_index',
views: {
email: {
map: function(doc) {
if (doc.email) emit(doc.email);
}.toString()
}
}
}
]
}
// init example db instance
async function initDb() {
db = new PouchDB('test', {
adapter: 'memory'
});
await db.bulkDocs(getDocsToInstall());
};
(async() => {
await initDb();
await view();
})();
<script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb-7.1.1.min.js"></script>
<script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb.memory.min.js"></script>
<pre>Info</pre>
<pre id='info'></pre>
<div style='margin-top:2em'></div>
<pre>email view</pre>
<pre id='view'>
</pre>
You can use GET /{db}, which returns information about the specified database. This is a JSON object that contains the property doc_count.
doc_count (number) – A count of the documents in the specified database.
With Angular for example, this could be done with the following method:
async countDocuments(database: string): Promise<number> {
return this.http.get<any>(this.url('GET', database), this.httpOptions).toPromise()
.then(info => info['doc_count']);
}
Assumption:
Assuming that following documents are present in the Customers database:
[
{
"_id": "93512c6c8585ab360dc7f535ff00bdfa",
"_rev": "1-299289ee89275a8618cd9470733035f4",
"name": "Tom",
"email": "tom#domain.com"
},
{
"_id": "93512c6c8585ab360dc7f535ff00c930",
"_rev": "1-a676883d6f1b5bce3b0a9ece92da6964",
"name": "Tom Doe",
"email": "tom#domain.com"
},
{
"_id": "93512c6c8585ab360dc7f535ff00edc0",
"_rev": "1-09b5bf64cfe66af7e1134448e1a328c3",
"name": "John",
"email": "john#domain.com"
},
{
"_id": "93512c6c8585ab360dc7f535ff010988",
"_rev": "1-88e347af11cfd1e40e63920fa5806fd2",
"name": "Alan",
"email": "alan#domain.com"
}
]
If I understand your query correctly, then based on above data, You need below given result set.
{
"tom#domain.com": 2,
"alan#domain.com": 1,
"john#domain.com": 1
}
Solution:
In order to achieve above, Consider following design document containing a View which has Map and Reduce functions.
{
"_id": "_design/Customers",
"views": {
"by-email": {
"map": "function (doc) {
if(doc.email){
emit(doc.email, doc._id);
}
}",
"reduce": "_count"
}
},
"language": "javascript"
}
The above view function emits value of the key email of the document if the key exists in the document.
The reduce function _count is a built in reducer (provided by CouchDB) that does the counting logic.
Executing View Query:
In order to query this view, you need to: select the view function, mark reduce to be executed (as it is optional to run reduce) and set 1 as group level.
Here is how you can do it through the UI:
Result:
Here is the result given by above query:
[![result of map reduce query
Hope this helped.
For more details about other reduce functions and group level, please refer CouchDB documentation.
Cheers.

How to insert Multiple collections using .forEach() and check if it exist or not

I want to insert multiple user details using .forEach() into my Mongodb Database.
Before insert records I want to check whether user record is exist or not. If user is not exist then insert new User record otherwise update the existing user record.
Below is
var dataArray=[
{"id":"1","name":"abc","email":"abc#gmail.com"},
{"id":"2","name":"xyz","email":"xyz#gmail.com"},
{"id":"1","name":"abc","email":"abc#gmail.com"},
];
dataArray.forEach(function(dataVar){
//check record exist or not
User.findOne({id:dataVar.id},function(err,user){
if(!user){// Insert If user not exist
var userSchema=new User({
id:dataVar.id,
name:dataVar.name,
email:dataVar.email
});
userSchema.save(function(err,result){
console.log('New Record Inserted');
})
}else{ // Update records if user exist
User.update({id:dateVar.id},{email:dataVar.email},function(err,result){
console.log('Record Updated');;
});
}
})
});
When run this code snippet, its checking only first object from array and insert in my DB. But next time when 3rd object going to execute then its not checking and inserting like a new record.
I am not getting whats going on.
Please let me know how to solve it.
Thanks.
You should do your loop async
See: https://caolan.github.io/async/docs.html#eachOfSeries
Example code
var dataArray = [{
"id": "1",
"name": "abc",
"email": "abc#gmail.com"
}, {
"id": "2",
"name": "xyz",
"email": "xyz#gmail.com"
}, {
"id": "1",
"name": "abc",
"email": "abc#gmail.com"
}, ];
async.eachOfSeries(dataArray, function(dataVar, key, callback) {
User.findOne({
id: dataVar.id
}, function(err, user) {
if (!user) { // Insert If user not exist
var userSchema = new User({
id: dataVar.id,
name: dataVar.name,
email: dataVar.email
});
userSchema.save(function(err, result) {
console.log('New Record Inserted');
callback();
})
} else { // Update records if user exist
User.update({
id: dateVar.id
}, {
email: dataVar.email
}, function(err, result) {
console.log('Record Updated');;
callback();
});
}
})
}, function(err) {
if (err) console.error(err.message);
// configs is now a map of JSON data
console.log("All done")
});

Trying to find record using $in in mongoose

I'm trying to find record using $in in mongoose. But it's not working for me. I have same query in mongo shell its working but in mongoose it is not workinh
my schema
{
"_id": "574f1f979f44e7531786c80f",
"name": "mySchool2",
"branch": "karachi",
"location": "Clifton ",
"block": false,
"created_at": 1464803223441,
"updated_at": 1464803223441,
"__v": 0,
"classes": [
"574f216afd487958cd69772a"
],
"admin": [
"574f20509f44e7531786c811",
"57508a2a3a0a919c16ace3c0"
],
"teacher": [
"574f20f39f44e7531786c812",
"575002b48188a3f821c2a66e",
"57500bbaea09bc400d047bf6"
],
"student": [
"574f2d56590529c01a2a473b",
"574f2e5842c5885b1b1729ab",
"574f2ed542c5885b1b1729ae",
"574f2f57555210991bf66e07",
"574f2fcd087809b11bd8d5e4",
"574f301d1f5025d61b7392b6",
"574f30481d02afff1bb00c71",
"574f30b01d02afff1bb00c74",
"574f310038136b3d1cf31b96"
]
}
My mongose query
app.services._chkAdminSchool = function(payload){
var deferred = q.defer();
School
//.find({ teacher:{$in:['574f20f39f44e7531786c812']}})
.find({_id : "574f1f979f44e7531786c80f",admin:{"$in":[Object("57508a2a3a0a919c16ace3c0")]}})
//.select(filers)
.exec(function(error, record) {
if (error) {
deferred.reject({
status:404,
message: 'Error in API',
error: error
});
} else {
if(record.length === 0){
deferred.reject({
message: 'Admin and school doesnot match',
data: record
});
}else{
deferred.resolve({
message: 'Successfully get Admin',
data: record
});
}
}
});
return deferred.promise;
}
records are return empty array.
Thanks in advance for help
The query you use works fine for me and returns object, make sure you define admin field in Schema like this:
"admin": [{type: mongoose.Schema.ObjectId }]
to avoid admin array object being string. If you compare Object("57508a2a3a0a919c16ace3c0") to "57508a2a3a0a919c16ace3c0" it will return empty array, check and replay to me, thanks.
I am not sure about how you define your School Schema, but if you are saving id in admin array as String, you don't need to change to Object in your mongoose query.
School.find({
_id: "574f1f979f44e7531786c80f",
admin: {
$in : ['57508a2a3a0a919c16ace3c0']
}
}, functin (err, record) {
...
})

Cascade delete from array using Mongoose middleware remove hook

I am building a Node.js express RESTfull API using Mongodb and mongoose.
This is my schema:
var UserSchema = new mongo.Schema({
username: { type: String },
password: { type: String, min: 8 },
display_name: { type: String, min: 1 },
friends: { type: [String] }
});
UserSchema.post('remove', function(next){
console.log({ friends: this._id }); // to test if this gets reached (it does)
UserSchema.remove({ friends: this._id });
});
And this is the function that removes a User:
.delete(function(req, res) {
User.findById(req.params.user_id, function(err, user) {
if (err) {
res.status(500);
res.send(err);
} else {
if (user != null) {
user.remove();
res.json({ message: 'User successfully deleted' });
} else {
res.status(403);
res.json({ message: 'Could not find user.' });
res.send();
}
}
});
});
What I need to do is when a user is removed, his or her _id (String) should also be removed from all the other users' friends array. Hence the remove hook in the schema.
Right now the user gets deleted and the hook gets triggered, but the user _id is not removed from the friends array (tested with Postman):
[
{
"_id": "563155447e982194d02a4890",
"username": "admin",
"__v": 25,
"password": "adminpass",
"display_name": "admin",
"friends": [
"5633d1c02a8cd82f5c7c55d4"
]
},
{
"_id": "5633d1c02a8cd82f5c7c55d4",
"display_name": "Johnybruh",
"password": "donttouchjohnsstuff",
"username": "John stuff n things",
"__v": 0,
"friends": []
}
]
To this:
[
{
"_id": "563155447e982194d02a4890",
"username": "admin",
"__v": 25,
"password": "adminpass",
"display_name": "admin",
"friends": [
"5633d1c02a8cd82f5c7c55d4"
]
}
]
To try and figure it out I have looked at the Mongoosejs Documentation, but the mongoose doc example doesn't cover the remove hook. Also this stackoverflow qestion but this question seems to be about removing from other schemas.
I think i'm doing the remove in the hook wrong, but I can't seem to find the problem.
Thanks in advance!
EDIT:
I could not get the first suggestion by cmlndz to work, so I ended up fetching all the documents with arrays that contained the to-be-deleted users' id and pulling it from them one-by-one:
The delete function now contains this bit of code that does the magic:
// retrieve all documents that have this users' id in their friends lists
User.find({ friends: user._id }, function(err, friends) {
if (err) {
res.json({ warning: 'References not removed' });
} else {
// pull each reference to the deleted user one-by-one
friends.forEach(function(friend){
friend.friends.pull(user._id);
friend.save(function(err) {
if (err) {
res.json({ warning: 'Not all references removed' });
}
});
});
}
});
You could use $pull to find all documents that contain the "ID" in the "friends" array -or- find any matching document and popping the "ID" out of the array one by one.

create a new object Id in mongoDB using node js

I am using the below code to insert data to mongodb
router.post('/NewStory', function (req, res) {
var currentObject = { user: userId , story : story , _id:new ObjectID().toHexString() };
req.db.get('clnTemple').findAndModify({
query: { _id: req.body.postId },
update: { $addToSet: { Stories: currentObject } },
upsert: true
});
});
This code is working fine if i remove the _id:new ObjectID().toHexString()
What i want to achieve here is that for every new story i want a unique _id object to be attached to it
What am i doing wrong?
{
"_id": {
"$oid": "55ae24016fb73f6ac7c2d640"
},
"Name": "some name",
...... some other details
"Stories": [
{
"userId": "105304831528398207103",
"story": "some story"
},
{
"userId": "105304831528398207103",
"story": "some story"
}
]
}
This is the document model, the _id that i am trying to create is for the stories
You should not be calling .toHexString() on this as you would be getting a "string" and not an ObjectID. A string takes more space than the bytes of an ObjectId.
var async = require('async'),
mongo = require('mongodb'),
db = require('monk')('localhost/test'),
ObjectID = mongo.ObjectID;
var coll = db.get('junk');
var obj = { "_id": new ObjectID(), "name": "Bill" };
coll.findAndModify(
{ "_id": new ObjectID() },
{ "$addToSet": { "stories": obj } },
{
"upsert": true,
"new": true
},
function(err,doc) {
if (err) throw err;
console.log(doc);
}
)
So that works perfectly for me. Noting the "new" option there as well so the modified document is returned, rather than the original form of the document which is the default.
{ _id: 55c04b5b52d0ec940694f819,
stories: [ { _id: 55c04b5b52d0ec940694f818, name: 'Bill' } ] }
There is however a catch here, and that is that if you are using $addToSet and generating a new ObjectId for every item, then that new ObjectId makes everything "unique". So you would keep adding things into the "set". This may as well be $push if that is what you want to do.
So if userId and story in combination already make this "unique", then do this way instead:
coll.findAndModify(
{
"_id": docId,
"stories": {
"$not": { "$elemMatch": { "userId": userId, "story": story } }
}
},
{ "$push": {
"stories": {
"userId": userId, "story": story, "_id": new ObjectID()
}
}},
{
"new": true
},
function(err,doc) {
if (err) throw err;
console.log(doc);
}
)
So test for the presence of the unique elements in the array, and where they do not exist then append them to the array. Also noting there that you cannot do an "inequality match" on the array element while mixing with "upserts". Your test to "upsert" the document should be on the primary "_id" value only. Managing array entries and document "upserts" need to be in separate update operations. Do not try an mix the two, otherwise you will end up creating new documents when you did not intend to.
By the way, you can generate an ObjectID just using monk.
var db = monk(credentials.database);
var ObjectID = db.helper.id.ObjectID
console.log(ObjectID()) // generates an ObjectID

Resources