searching an array of values against mongoose - node.js

I am not sure how I need to do what I'm wanting to do. My schemas are like this:
var userObj = mongoose.Schema({
'timestamp':{type: Date, default: Date.now()},
'password':{type:String, required:true},
"userName":{type:String, required:true, unique:true}
});
var groupSchema = mongoose.Schema({
'creator':String,
'members':Array, //contains the _id of users added to the group
'admins':Array,
'name':String,
'timestamp':{type: Date, default: Date.now()},
'description':String
});
Where the members schema has an array that contains IDs of users. I need to take the array from the group document and get back user names.
I started out with a loop using the mongoose .find() method and pushing the result into an array - but as I expected the array is empty outside of the scope of the callback function.
var dat = [];
for(var i = 0; i<passed.payload.length;i++){
user.find({'_id':passed.payload[i]},'userName',function(err,result){
if(err){
console.log(err);
}else{
dat.push(result);
}
})
}
//res.send(dat)
console.log(dat);
I am not sure how to do this - I considered using .find to pull all user IDs then running the array to return back only matches. That seems like a waste of resources to pull the full users table then test it.
Is there a more complex query I can use with mongoose to pull data like this using an array of _ids to match?

Have you considered using the $in operator?
db.users.find( { userName: { $in: ["Paul", "Jenna" ] } } )
This should be more efficient and removes the loop as well.
here a link to the MongoDB docs
https://docs.mongodb.com/manual/reference/operator/query/in/#op._S_in

The reason you are getting an empty array outside for loop is mongoose queries are asynchronous (in your case User.find()) and therefore for loop will be completed before mongoose queries are completed resulting into empty array you are getting. You can handle this either by using promises or some npm module like async.
However, as #karlkurzer suggested you don't need to loop through an array here, you can instead use $in operator. Above code can be replaced with,
user.find({'_id': {$in: passed.payload}},'userName',function(err,results){
if(err){
console.log(err);
}else{
console.log(results); //You will get array of user names here
}
});
and you should get an array of userNames associated with ids you passed in callback.
Hope this helps!

Related

Mongoose - get length of array in model

I have this Mongoose schema:
var postSchema = mongoose.Schema({
postId: {
type: Number,
unique: true
},
upvotes: [
{
type: Number,
unique: true
}
]
});
what the best query to use to get the length of the upvotes array? I don't believe I need to use aggregation because I only want to query for one model, just need the length of the upvotes array for a given model.
Really struggling to find this info online - everything I search for mentions the aggregation methodology which I don't believe I need.
Also, as a side note, the unique schema property of the upvotes array doesn't work, perhaps I am doing that wrong.
find results can only include content from the docs themselves1, while aggregate can project new values that are derived from the doc's content (like an array's length). That's why you need to use aggregate for this, even though you're getting just a single doc.
Post.aggregate([{$match: {postId: 5}}, {$project: {upvotes: {$size: '$upvotes'}}}])
1Single exception is the $meta projection operator to project a $text query result's score.
I'm not normally a fan of caching values, but it might be an option (and after finding this stackoverflow answer is what I'm going to do for my use case) to calculate the length of the field when the record is updated in the pre('validate') hook. For example:
var schema = new mongoose.Schema({
name: String,
upvoteCount: Number,
upvotes: [{}]
});
schema.pre('validate', function (next) {
this.upvoteCount = this.upvotes.length
next();
});
Just note that you need to do your updates the mongoose way by loading the object using find and then saving changes using object.save() - don't use findOneAndUpdate
postSchema.virtual('upvoteCount').get(function () {
return this.upvotes.length
});
let doc = await Post.findById('foobar123')
doc.upvoteCount // length of upvotes
My suggestion would be to pull the entire upvotes fields data and use .length property of returned array in node.js code
//logic only, not a functional code
post.find( filterexpression, {upvote: 1}, function(err, res){
console.log(res.upvotes.length);
});
EDIT:
Other way of doing would be stored Javascript. You can query the
upvote and count the same in mongodb side stored Javascript using
.length

Mongoose $push cannot push object into correct document

I have a mongoose schema like this:
A = {
_id: Schema.Types.ObjectId,
arrayA:[{
_id,
nestedArray: [Schema.Types.ObjectId]
}],
arrayB: [Schema.Types.ObjectId]
}
I would like to push an Object Id into nestedArray in specific arrayA object AND
arrayB should contains an specific Object Id by following code:
A.update({'arrayA._id': arrayAId, arrayB: {$in: [arrayContainsSomeArrayBIds]}},
{$push: {'arrayA.$.nestedArray': nestedArrayId}}, function(err) {
});
However, the Object Id is pushed into nestedArray of the last object in arrayA.
If arrayB: {$in: [arrayContainsSomeArrayBIds]} is removed, the Object Id can be pushed into correct object in arrayA.
mongoose version: 3.8.21
Can anyone help me to find out the problem?
Currently it is not possible in MongoDB to update an a array element with the positional operator, when the query document contains references to other arrays apart from the one being updated.
The below code, contains reference to two arrays fields: arrayA and arrayB, when
the update is issued on arrayA. This is invalid and would lead to undesired behavior.
A.update({'arrayA._id': arrayAId, arrayB: {$in: [arrayContainsSomeArrayBIds]}},
{$push: {'arrayA.$.nestedArray': nestedArrayId}}, function(err) {
});
From the docs,
Only one array field may appear in the query document.
The query document should only contain a single condition on the array field
being projected.
Multiple conditions may override each other
internally and lead to undefined behavior.
Under these requirements,
the following query is incorrect:
db.collection.find( { <array>: <value>, <someOtherArray>: <value2> },
{ "<array>.$": 1 } )
The solution is to modify your code to fire two queries:
Get the _ids of the documents, which match our condition.
Then perform the update.
Sample Code flow:
A.find({'arrayA._id': arrayAId, arrayB: {$in: [arrayContainsSomeArrayBIds]}},
function(err,data){
data.forEach(function(doc){
A.update({'arrayA._id': arrayAId,
"_id":doc._id},
{$push: {'arrayA.$.nestedArray': nestedArrayId}},
function(err) {
});
})
});

Using the find method on a MongoDB collection with Monk

I am working through a MEAN stack tutorial. It contains the following code as a route in index.js. The name of my Mongo collection is brandcollection.
/* GET Brand Complaints page. */
router.get('/brands', function(req, res) {
var db = req.db;
var collection = db.get('brandcollection');
collection.find({},{},function(e,docs){
res.render('brands', {
"brands" : docs
});
});
});
I would like to modify this code but I don't fully understand how the .find method is being invoked. Specifically, I have the following questions:
What objects are being passed to function(e, docs) as its arguments?
Is function(e, docs) part of the MongoDB syntax? I have looked at the docs on Mongo CRUD operations and couldn't find a reference to it. And it seems like the standard syntax for a Mongo .find operation is collection.find({},{}).someCursorLimit(). I have not seen a reference to a third parameter in the .find operation, so why is one allowed here?
If function(e, docs) is not a MongoDB operation, is it part of the Monk API?
It is clear from the tutorial that this block of code returns all of the documents in the collection and places them in an object as an attribute called "brands." However, what role specifically does function(e, docs) play in that process?
Any clarification would be much appreciated!
The first parameter is the query.
The second parameter(which is optional) is the projection i.e if you want to restrict the contents of the matched documents
collection.find( { qty: { $gt: 25 } }, { item: 1, qty: 1 },function(e,docs){})
would mean to get only the item and qty fields in the matched documents
The third parameter is the callback function which is called after the query is complete. function(e, docs) is the mongodb driver for node.js syntax. The 1st parameter e is the error. docs is the array of matched documents. If an error occurs it is given in e. If the query is successful the matched documents are given in the 2nd parameter docs(the name can be anything you want).
The cursor has various methods which can be used to manipulate the matched documents before mongoDB returns them.
collection.find( { qty: { $gt: 25 } }, { item: 1, qty: 1 })
is a cursor you can do various operations on it.
collection.find( { qty: { $gt: 25 } }, { item: 1, qty: 1 }).skip(10).limit(5).toArray(function(e,docs){
...
})
meaning you will skip the first 10 matched documents and then return a maximum of 5 documents.
All this stuff is given in the docs. I think it's better to use mongoose instead of the native driver because of the features and the popularity.

Updating array within mongodb record with mongoose

What is the best way to update a value within an array saved in a mongodb record? Currently, I'm trying it this way:
Record.find({ 'owner': owner}, {}, {sort: { date: -1 }}, function(err, record){
if(!err){
for (var i = 0; i < record[0].array.length; i++){
record[0].array[i].score = 0;
record[0].array[i].changed = true;
record[0].save();
}
}
});
And the schema looks like this:
var recordSchema = mongoose.Schema({
owner: {type: String},
date: {type: Date, default: Date.now},
array: mongoose.Schema.Types.Mixed
});
Right now, I can see that the array updates, I get no error in saving, but when I query the database again, the array hasn't been updated.
It would help if you explained your intent here as naming a property "array" conveys nothing about its purpose. I guess from your code you hope to go and set the score of each item there to zero. Note your save is currently being ignored because you can only save top-level mongoose documents, not nested documents.
Certain find-and-modify operations on arrays can be done with a single database command using the Array Update Operators like $push, $addToSet, etc. However I don't see any operators that can directly make your desired change in a single operation. Thus I think you need to find your record, alter the array date, and save it. (Note findOne is a convenience function you can use if you only care about the first match, which seems to be the case for you).
Record.findOne({ 'owner': owner}, {}, {sort: { date: -1 }}, function(err, record){
if (err) {
//don't just ignore this, log or bubble forward via callbacks
return;
}
if (!record) {
//Record not found, log or send 404 or whatever
return;
}
record.array.forEach(function (item) {
item.score = 0;
item.changed = true;
});
//Now, mongoose can't automatically detect that you've changed the contents of
//record.array, so tell it
//see http://mongoosejs.com/docs/api.html#document_Document-markModified
record.markModified('array');
record.save();
});
If you have a mongoose object of a document, you can of course update the array as in the question, with the following Caveat.
This is in fact a mongoose gotcha. Mongoose cannot track changes in the array of mixed, one has to use markModified:
doc.mixed.type = 'changed';
doc.markModified('mixed.type');
doc.save() // changes to mixed.type are now persisted

finding objectIds between two given values in mongodb and nodejs

I am creaing schemas similar to newsposts with an option for users to like and dislike them.
Here are the schemas for same
Client= new mongoose.Schema({
ip:String
})
Rates = new mongoose.Schema({
client:ObjectId,
newsid:ObjectId,
rate:Number
})
News = new mongoose.Schema({
title: String,
body: String,
likes:{type:Number,default:0},
dislikes:{type:Number,default:0},
created:Date,
// tag:String,
client:ObjectId,
tag:String,
ff:{type:Number,default:20}
});
var newsm=mongoose.model('News', News);
var clientm=mongoose.model('Client', Client);
var ratesm=mongoose.model('Rates', Rates);
In order to retreive the ratingsgiven by a particular user having given a set of newsposts, I tried,
newsm.find({tag:tag[req.params.tag_id]},[],{ sort:{created:-1},limit: buffer+1 },function(err,news){
ratesm.find({
client:client._id,
newsid:{$lte:news[0]._id,$gte:news.slice(-1)[0]._id}
},
function(err,ratings){
})
})
This query returns empty list no matter what. I doubt whether $gte and $lte be used to compare objectIds. Am I right? How can I which posts a user has liked/disliked in a given set of newsposts?
Yes, ObjectIds can be queried with range queries like $gt/$lt etc. Can you post the exact values being used for news[0]._id and news.slice(-1)[0]._id that are giving you the empty result?
However, i'm not sure that $gt/$lt is what you want here. It seems like what you need to do is extract the _ids of the news items, and then use that in a $in filter in your query on ratesm.

Resources