complex mongoose query with $where - javascript

Im having a real headache with a mongoose query, I am still quite new to it and wondered if anyone could suggest a solution.
I have the following fields in a gallery collection.
status (value of 'public' or 'restricted'),
restriction (value of 'canview' or 'cantview'),
canview (an array of user ids (ObjectIds) of users permitted to view gallery)
I then check fields against the current userID, to see what items are viewable.
how would it be possible to select ALL of the following items
a) all items with 'status' = 'public'
b) all items with 'status' = 'restricted' WHERE 'restricted' = 'canview' AND 'canview' contains the UserID
I wasnt able to do this using $and and $or, so tried with $where instead. Using the following global function
var existsInArray = function(str,arr) {
// nb: remember we have typeof ObjectID, so lets convert to strings first!
var newarr = arr.toString().split(',');
// now compare..
if(newarr.inArray(str)) {
return true;
}
return false;
};
I was hoping to do something like this...
exports.listviewable = function(req, res) {
var reqID = req.user.id;
Gallery
.find()
.$where(function () {
return this.status === "public" || ( this.status === "restricted" && this.restriction === "canview" && existsInArray(reqID, this.canview));
})
.sort('-created')
.exec(function(err, galleries) {
if(err) {
return res.status(400).send({message:getErrorMessage(err)})
} else {
res.json(galleries);
}
});
};
but this wasnt working - it appears I wasnt able to use the global existsInArray function in the $where clause ?
Is it possible to do it like this ( similar unresolved question here how to call external function in mongoose $where and passing this variable), or is there a better way to do it with AND and OR ?

Hey I'd recommend hitting the mongo docs - they have alot of info on this.
a)
Gallery.find({status: 'public'}, function(err, data){
console.log(data)
});
b)
Gallery.find({status: 'restricted', restricted: 'canview', canview: reqID }, function(err, data){
});
or both together with sorting...
Gallery.find({$or:[
{status: 'public'},
{status: 'restricted', restricted: 'canview', canview: reqID }
]}, {
sort:{created:-1}
}, function(err, data){
console.log(data)
});

A $where function can't refer to your local existsInArray function because the $where function is executed on the server where that local function doesn't exist.
But you shouldn't be using $where anyway, as you can do with with a simple $or query that takes advantage of the fact that you can directly query array fields like canview with "contains" behavior:
Gallery
.find({
$or: [
{status: 'public'},
{status: 'restricted', restriction: 'cantview', canview: reqId}
]
})
.sort('-created')
.exec(function(err, galleries) {...});

Related

Why am I not being able to write into objects I fetched from database?

What I am trying to do: I am fetching data from MongoDB(doc). I has property files which is an array of objects. Schema of the mentioned object:
{
fileName : {type : String, required : true},
fileSize : {type : Number, required : true},
fileOriginalName : String,
sentEarlier : Boolean,
}
and before sending this array(in doc.files) to my frontend, I want to append a property(downloadLink), to each object. The whole code where I encountered the problem:
Router.post('/fetch-more-files', (req, res) => {
let {uuid} = req.body;
File.findOne({uuid}, (err, doc) => {
if(err) {
return res.send({'error' : 'Something went wrong. Refresh the page.'});
}
if(!doc) {
return res.send({'error' : 'Invalid link or link expired'});
}
let newFiles = [];
doc.files.forEach(file => {
file.downloadLink = `${process.env.APP_BASE_URL}/files/download/${uuid}/${file.fileName}`;
});
doc.files.forEach(file => {
if(!file.sentEarlier) newFiles.push(file);
});
doc.save((err, savedDoc) => {
res.json({newFiles});
});
});
});
PROBLEM: The newFiles array which I am populating with all the files(objects inside doc.files array), those that do not have a 'sentEarlier' key true to them, when sent as response, not a single object that array has the 'downloadLink' property to them. I tried so hard and for so long to debug, but failed. Hope to find some insights as to what I could be doing wrong here. Thanks in advance!!!
SOLUTION: After hours of lingering and loitering over this problem, I found a solution. If you want the technical answer to why it didn't work, this is not the place for you. But if you just want your problem solved, so you can finally go pee, read on.
The answer is to use toObject() method which apparently converts the mongoose document to a plain JS object, which is now at your disposal.
toObject() in mongooseJS documentation:
Mongoose v5.11.9:API docs | document.prototype.toObject()
Other answers are most welcome.
Mongoose queries return an instance of the Mongoose Document class. if you want to modify your query response just try to call your mongoose query in a different way.
File.findOne({uuid}, {new: true}).lean().exec((err, doc) => {
if(err) {
return res.send({'error' : 'Something went wrong. Refresh the page.'});
}
// your logic
});
here you get more idea about lean()

Meteor MongoDB Setting field of array per document

I have a Documents in a Collection that have a field that is an Array (foo). This is an Array of other subdocuments. I want to set the same field (bar) for each subdocument in each document to the same value. This value comes from a checkbox.
So..my client-side code is something like
'click #checkAll'(e, template) {
const target = e.target;
const checked = $(target).prop('checked');
//Call Server Method to update list of Docs
const docIds = getIds();
Meteor.call('updateAllSubDocs', docIds, checked);
}
I tried using https://docs.mongodb.com/manual/reference/operator/update/positional-all/#positional-update-all
And came up with the following for my Server helper method.
'updateAllSubDocs'(ids, checked) {
Items.update({ _id: { $in: ids } }, { $set: { "foo.$[].bar": bar } },
{ multi: true }, function (err, result) {
if (err) {
throw new Meteor.Error('error updating');
}
});
}
But that throws an error 'foo.$[].bar is not allowed by the Schema'. Any ideas?
I'm using SimpleSchema for both the parent and subdocument
Thanks!
Try passing an option to bypass Simple Schema. It might be lacking support for this (somewhat) newer Mongo feature.
bypassCollection2
Example:
Items.update({ _id: { $in: ids } }, { $set: { "foo.$[].bar": bar } },
{ multi: true, bypassCollection2: true }, function (err, result) {
if (err) {
throw new Meteor.Error('error updating');
}
});
Old answer:
Since you say you need to make a unique update for each document it sounds like bulk updating is the way to go in this case. Here's an example of how to do this in Meteor.
if (docsToUpdate.length < 1) return
const bulk = MyCollection.rawCollection().initializeUnorderedBulkOp()
for (const myDoc of docsToUpdate) {
bulk.find({ _id: myDoc._id }).updateOne({ $set: update })
}
Promise.await(bulk.execute()) // or use regular await if you want...
Note we exit the function early if there's no docs because bulk.execute() throws an exception if there's no operations to process.
If your data have different data in the $set for each entry on array, I think you need a loop in server side.
Mongo has Bulk operations, but I don't know if you can call them using Collection.rawCollection().XXXXX
I've used rawCollection() to access aggregate and it works fine to me. Maybe work with bulk operations.

Apply filter if value exists otherwise ignore - mongoose

I have the following field within my collection:
"intent" : [
"Find a job",
"Advertise a job"
],
What I'm trying to do is basically filter results on that field.
So if the end user passes in
[
"Find a job",
]
I would then return all documents that have the intent Find a job this is my query so far:
var _intent = req.body.intent;
Reason.find({ intent: { $in: _intent }}, function (err, reason) {
if (err) {
res.send(err);
}
res.json(reason);
});
Now this works when I specify a value for intent as I'm using the $in-Logical Query Operator however when I do not specify a value for intent it fails to return any documents, so what I'm asking is, is there a way I can say if intent has a value then filter on it, otherwise if it doesn't then do not apply the filter?
Why not set the query object dynamically?
var query = {};
if ( req.body.hasOwnProperty('intent') )
query.intent = { "$in": req.body.intent };
Reason.find(query, function(err, reason) {
});
Or even
if ( req.body.intent.length > 0 )
Which ever case of logic suits. It's all just JavaScript after-all.

Infinite scroll with Angularjs and Mongoose - performance

I have a list of images that I get with Angular using $http.get() from MongoDB using Mongoose and Expressjs.
What I did works fine but I have a doubt about performance.
So far I found two ways to do it:
using skip
using $nin
The queries looks like this:
// Using $nin:
var skip = req.query.skip || [];
User.find({ _id : { $nin: skip }})
.sort({ _id: -1 })
.limit(15)
.exec(function(err, users) {
if (err) res.json({ 'msg': 'Error loading users' });
res.json({
users: users
});
});
and:
// Using skip
User.find({})
.sort({ _id: -1 })
.skip(15)
.limit(15)
.exec(function(err, users) {
if (err) res.json({ 'msg': 'Error loading users' });
res.json({
users: users
});
});
Googleling around it seems like using skip lack of performance after a while...
But looking at the $nin option I found after scrolling and scrolling a very long query... with plenty of _id...
Which of the 2 solution should be better to use?
Or there is a third way much better than those, maybe I'm doing something wrong?
Thanks
Ideally you want to use $nin with a list of previously seen _id values but combine that with a $gte or $lte (depending on order) operator on something you are sorting on.
This is generally the case for most "otherwise sorted queries", but in the case where the _id field is the one you are sorting on ( and in decending order ) it just becomes a matter of working with $lt to find values that are less than the "last seen value" from the previous page.
So when iterating, store the "last seen value" from the last item in your "page limit" results, then use the $lt operator in subsequent queries:
ie:
var lastSeen = null; // declare in a global or session or something
User.find({})
.sort({ "_id": -1 })
.limit(15)
.exec(function(err,docs) {
lastSeen = docs.slice(-1).id;
});
And then:
User.find({ "_id": { "$lt": lastSeen })
.sort({ "_id": -1 })
.limit(15)
.exec(function(err,docs) {
lastSeen = docs.slice(-1).id;
});

No results retrieving document by UUID in MongoDB

I am trying to retrieve a document from MongoDB. The document has the field "ownerId" that contain a binary UUID. Using the Mongo console if I launch the command
db.dataset.find({ownerId: BinData(3,"ZQ6EAOKbQdSnFkRmVUUAAA==")}).pretty()
it returns:
{
"_id" : BinData(3,"VQ6EAOKbQdSnFkRmVUUAAA=="),
"name" : "Twitter",
"objectType" : "Tweet",
"ownerId" : BinData(3,"ZQ6EAOKbQdSnFkRmVUUAAA==")
}
When I try to retrieve the document from my node.js program, it fails and does not return any document.
My program is:
var mongo = require('mongoskin');
var db = mongo.db("mongodb://192.168.1.100:27017/test", {native_parser:true});
function HexToBase64(g) {
...
}
var uuidstr = "650e8400e29b41d4a716446655450000";
console.info(uuidstr);
base64str = HexToBase64(uuidstr);
console.info(base64str);
db.collection('dataset').find( { ownerId:new mongo.Binary(base64str, 4) } ).toArray(function (err, items) {
if(err) {
var msg = "Error getting 'dataset' objects from database.";
console.info(msg + " " + err);
return;
}
console.info("OK");
console.info(items);
});
The output is:
650e8400e29b41d4a716446655450000
ZQ6EAOKbQdSnFkRmVUUAAA==
OK
[]
What am I doing wrong?
Firstly, since you're going to query the collection by OwnerId, I'd suggest to create an index there. You can achieve that in mongoskin easily enough:
// Create and index by ownerId
db.collection('dataset').ensureIndex([['ownerId', 1]], true, function(err, res) {
if (err) throw err;
});
Once you've done that, you can now do things like this:
db.collection('dataset').findOne({ ownerId: 'some-uuid' }, callback);
Further, if it's the ObjectId type you're after, I'd recommend you use the provided helper (see this other question) to achieve that:
var ownerId = mongoskin.ObjectID(uuidstr);
Otherwise, I'd go with a different approach, perhaps using node-uuid :
var uuid = require('node-uuid');
var ownerId = uuid.v4();
db.collection('dataset').findOne({ ownerId: ownerId }, callback);
Hope this helps,

Categories