Query mongo document based on its subdocument using $exists - node.js

I am trying to fetch records from mongo using mongoose.
I have document called projects that has sub-document work. work has user, to and from properties. I want to fetch the document that has specific user, and doesn't have to value set.
So far i tried:
models.project.findOne {'work.user': user,'work.to':{$exists: false}},
(err, data) -> # data is always null, no error.
It seems that if just one of objects in work contains property 'to' then $exists: equals to true. Is there a way to do this?
Also here is same code in JS if you prefer:
models.project.findOne({
'work.user': user,
'work.to': {
$exists: false
}
}, function(err, data) {});
Thanks for your time.

You can use $elemMatch in this case, which will return results only when specific user matched and doesn't have to value set in the same array element:
models.project.users.findOne({
"work" : { $elemMatch: {"user":user, "to": {$exists: false}}}
}, function(err, data) {});

you could use "null" for this case instead of $exists
models.project.findOne({
'work.user': user,
'work.to': null
}, function(err, data) {});

Related

MongoDB upsert creates another instance

I currently have an upsert function in my project which works but my main problem is that it creates another instance of the record, and updates the new instance instead. This is the code:
router.route('/carousel/update/:_id').put(function(req, res) {
var id;
if(req.params._id == 'undefined'){
id = crypto.randomBytes(12).toString('hex');
}
else {
id = ObjectId(req.params._id)
}
db.collection('home').updateOne({"_id": id},
{$set: req.body}, {upsert: true}, (err, results) => {
if (err) throw err;
res.send(results)
console.log(req.body)
});
});
The problem:
1. It mystifies me that mongoDB takes my crypto generated _id and takes it as the new _id for the upserted document. Why is that? When {upsert: true}, isn't mongoDB supposed to generate a new _id?
2. Because of the nature of problem 1, whenever I try to update the original document, it updates the upserted document instead since they have the same _id values even though their _ids are positioned at different document levels.
In conclusion, when given a 'home' document, how do I upsert correctly without adding a new record with the same values and _ids?
Thanks for your help!
EDIT
This is the JSON body content of the document with custom generated _id using crypto:
{
"_id": "1262d480eea83567181b3206",
"header": "hello",
"subheader": "hello"
}
Whereas, this is the body content of the upserted document.
{
"_id": {
"$oid": "1262d480eea83567181b3206"
},
"header": "helloasad",
"subheader": "helloasda"
}
As observed, after upserting, it takes the same _id value of the original document but on another document level.
A possible solution/explanation based on #Ashwanth Madhav information:
In your code 'id' was being sent to the update as a String type, but the id in MongoDB is an ObjectId type:
Code will be something like that:
var id;
if(req.params._id == 'undefined'){
// 'id' NEED TO BE AN ObjectId...
// 'id' WAS BEING SENT AS A 'String'
id = ObjectId(crypto.randomBytes(12).toString('hex'));
}
else {
id = ObjectId(req.params._id)
}

MongoDB $push operator not working as expected in Node

So I have a fairly simple piece of code as follows
db.get().collection('bars').insert({
barID: req.body.button,
}, {
$push: {
usersfbID: req.body.profileUser[0].facebookID
}
}, function(err, doc) {
if (err) {
throw err;
}
if (doc) {
console.log('Had to create a new document for this bar');
console.log(doc);
//callback(null, doc);
}
});
So, I'm just checking to see if a document for a bar exists, and if it doesn't then I create that document. And I want to insert an array for the usersfbID field so that I can store all the users going to the bar.
However, when I run the code, I don't get an error and it says the document has inserted but when the document logs, it doesn't have the userfbID field.
So what am I doing wrong? Does the $push operator only work with the update method of db? If so, how do I insert an array for that field?
Yes, it does work with the update methods
Reference > Operators > Update Operators > Array Update Operators > $push
Inserting a new entry means feeding the fields. In that case, there's no $push operation, since the array of the entry is freshly created and can be explicitly set (usersfbID:[req.body.profileUser[0].facebookID], meaning that you expect several fbId for that bar). Updating an array in an element of a collection isn't an insertion, it's an update.
So, just to provide an answer to the question I was facing..
Yes, you can only use $push or $addToSet with an update operation on a mongoDB document
Here is the way I implemented the code.
db.get().collection('bars').update({
barID: req.body.button,
}, {
$addToSet: {
usersfbID: req.body.profileUser[0].facebookID,
usersDocID: req.body.profileUser[0]._id
}
}, {
upsert: true
}, function(err, doc) {
if (err) {
console.log('There is an error here');
throw err;
}
if (doc) {
console.log('Had to create a new document for this bar');
callback(null, doc);
}
});
The upsert: true makes sure to insert a new document if the update method couldn't find the specified document.

Sails + Mongo + Waterline. Sort by count documents in populated collection

I have two models - User and Tag. Part of User model
module.exports = {
attributes: {
tags: {
collection: "tag",
via: "users"
}
}};
Tag model looks like
module.exports = {
attributes: {
name: {
type: 'string',
required: true
},
users: {
collection: "user",
via: "tags"
},
}};
And I need to sort tags by "users.length" (each tag can has different number of users). I tried this
Tag.find().populate('users').sort('users.length DESC').exec(function(err, tags){});
And also I tried something like this
Tag.native(function (err, collection) {
if (err) return res.serverError(err);
collection.aggregate(
[
{ $sort : { users : -1 } }
], function (err, results) {
});
});
But I had a fail. Documents are not sorted in right order. Can you help me to sort documents as I want please? I'm a new in Sails and in Mongo too. And I have no idea how I can do this.
P.S.
I use sails-mongo adapter version 0.11.2
Unfortunately, there's no way of doing this as of now.
I would suggest maintaining a count field in the Tag model itself and updating it each time a User adds or removes a Tag. Then run a normal query like you did with count as the sort key. Doing this also allows you to query faster if you just need Tag information (and not the users) since you can skip the populate() call which internally references another collection.

Mongoose query where with missing (or undefined) fields

I have an issue that I have a messy MongoDB database with a revised Mongoose scheme.
I want to be able to query using the where command, but if the field is missing or undefined in any found record then Mongoose returns an error.
For instance:
return this.find({personID: req.user._id})
.where('rangeBefore').lt(time);
This returns an error because some records do not have rangeBefore.
What I want to achieve is that any record without the rangeBefore field is filtered out (i.e. it fails the where test).
I have tried prefixing with exists like this:
return this.find({personID: req.user._id})
.exists('rangeBefore').where('rangeBefore').lt(time);
but I still get an error.
Can anyone suggest a way to ignore records with undefined fields, rather than returning an error?
If you just want to modify your existing query:
return this.find({ personID: req.user._id,
rangeBefore: { $exists: true, $ne: null } //also check for nulls
})
.where('rangeBefore').lt(time);
Or you can use the callback approach too if it fits:
this.find({personID: req.user._id,
rangeBefore: { $exists: true, $ne: null, $lt: time }
})
.exec(function(err, record) { });
You can do it like this, which returns a promise:
find({rangeBefore: { $exists: 1, $lt: time } }).exec()
Or use a callback:
find({rangeBefore: { $exists: 1, $lt: time } }).exec(function(err, record){
})
Your error message of:
Cast to number failed for value "undefined" at path "rangeBefore"
means that your time variable is undefined. A query like this will naturally ignore docs where rangeBefore doesn't exist.
So the problem is with your time variable, not your docs or query.

$addToSet and upsert

I know I can't use upsert and the positional operator together, but I am looking for away to append to array if some fields of the object I am inserting do not match some fields in an existing object within the array.
so if I have the existing document below, I would like to check for 'field' field's value and update/replace that subdocument if the fields match, and simply append to the array if they don't.
{
myArray:[
{
field:'xyz'
}
]
}
Is there a good way to do this in node.js? I'm using the native driver.
I don't think you can do this in a single query. You can do this with two separate queries in the native driver. The first query would try to update the field in the array. If it doesn't find the document in the array that matches, it dispatches a second query to append the document to the array.
db.collection('coll').update({_id: _id, "myArray": {field: "xyz"}}, {"$set": {"myArray.$": {field: "xyzt"}}}, {upsert: true}, function(err, res) {
if (err && err.code == 16836) { // no document was matched
db.collection('coll').update({_id: _id}, {"$push": {myArray: {field: "xyzt"}}}, function(err, res) {
console.log("Inserted document in array");
});
}
console.log("Updated document in array");
});

Resources