How to use mongoose populate and moongose pagination together? - node.js

Full code here: https://github.com/kenpeter/dl_r18_img_back
There is a nice package to hook pagination with mongoose:
According to this, it seems I can use populate with pagination, but I am not able to get it working.
list: function(page = 1, limit = 100){
return new Promise(function(resolve, reject){
let options = {
page: parseInt(page),
limit: parseInt(limit),
sort:{
createdDate: -1 //Sort by Date Added DESC
}
};
/*
Image
.paginate({}, options)
.then(function(res) {
resolve && resolve(res);
});
*/
// NOT WOKRING!!!!!
Image
.populate('category')
.execPopulate()
.paginate({}, options)
.then(function(res) {
resolve && resolve(res);
});
/*
Image
.find({})
..populate('category')
.exec()
.then(function(res) {
resolve && resolve(res);
});
*/
});
As you can see there are 2 commented-out code block Image.xxxxx. They are working individually.
How do I put them together?

The documentation says to put a populate option for populating.
Here is a snippet from the doc:
var options = {
select: 'title date author',
sort: { date: -1 },
populate: 'author',
lean: true,
offset: 20,
limit: 10 };
maybe use populate : "category"

Related

How to update many documents on mongo and return that updated document?

How can I update many documents on mongoose and return those updated documents so I then I can pass the updated docs to a different service on my code? This is seems like something simple to do but I'm getting confused on my head on how to implement it
In my current code I just update the documents on batch with updateMany, but as the mongo documentation says the writeConcern returned is just the # of docs updated {n: 0 } not the actual documents.
Current Code:
const checkAndUpdateSubscription = () => {
const filter = {
"payments.0.stripeSubscriptionEndDate": { $lte: today },
hasPaid: true,
};
const update = { $set: { hasPaid: false, isSubscriptionNew: 0 } };
const options = { new: true, useFindAndModify: false };
return new Promise((resolve, reject) => {
ModelModel.updateMany(filter, update, options)
.then((response) => {
console.log('response inside checkAndUpdateSubscription', response)
resolve(response);
})
.catch((error) => {
reject(error);
});
});
};
I would like to change it to something similar to my pseudo code below.
What I would like to do:
const checkAndUpdateSubscription = () => {
const filter = {
"payments.0.stripeSubscriptionEndDate": { $lte: today },
hasPaid: true,
};
const update = { $set: { hasPaid: false, isSubscriptionNew: 0 } };
const options = { new: true, useFindAndModify: false };
return new Promise((resolve, reject) => {
// 1. ModelModel.find where stripeSubscriptionEndDate $lte than today ..etc
// 2. Update the document(s)
// 3. Return the updated document(s)
(//4.) .then(updatedModel => sendUpdateModelToOutsideService(updatedModel))
});
};
I don't know if this is necessary in the context of this question but the checkAndUpdateSubscription method is a function that runs every 1min in my db for all my users (# ~thousands)
You can do those as alternative solutions
(maybe there is simpler way, but those will work i think)
Find the ids
find to get those ids
ids = find(your_filter,project_keep_id_only), and then make ids=[_id1, _id2 ...] an array of the ids
update with filter _id $in ids
update({"_id" : {"$in" : ids}},your_update_set_etc)
find to get the updated documents
docs=find({"_id" : {"$in" : ids}})
*if ids are not too many it will be fine i think
Mark the updated with extra field
on update set one extra field with the update_id
after the update, do a find based on that update_id, to get the documents
and if you want your remove that extra field after
If this run parallel, this extra field could be an array, with many update_ids, that you remove them after you get those documents back.

Mongoose: updateMany() is not working as expected

I'm using mongoose to handle my DB queries. I'm trying to update a set of records entirely using this method. Mode code looks like this:
// prepare database query
const filter = { type: 'company' };
const update = req.body.payload; // payload contains the array of objects (i.e. updated records)
const options = { new: true, runValidators: true }
// find and update the taxonomy record
await Taxonomy.updateMany(filter, update, options);
But whenever I run this query I'm getting following error in the console:
Error [MongooseError]: Invalid update pipeline operator: "_id"
I suppose there is something wrong in my update payload. The req.body.payload looks like this:
[
{
_id: '5ef3d08c745428001d92f896',
type: 'company',
name: 'Company Size',
__v: 0
},
{
_id: '5ef3cdc5745428001d92f893',
type: 'company',
name: 'Company Industry',
__v: 0
}
]
Can you please tell me what actually is wrong here?
This is not the right usage of updateMany() - it is aimed to update many documents with a single change.
To update many documents use bulkwrite() (docs) :
async function myUpdateMany(Model, objectsArray) {
try {
let ops = []
for (let obj of (objectsArray || [])) {
ops.push({
updateOne: {
filter: { platformId: obj.platformId },
update: obj,
upsert: false, // set "true" if you want to add a new document if it doesn't exist
}
})
}
Model.bulkWrite(ops, { ordered: false });
} catch (err) {
throw Error("myUpdateMany error: " + err)
}
}
Regarding runValidators, according to this, it seems to work by default.

How do I keep mongo's array index from changing during an update?

I am trying to update an array within my object. However, every time I send the post call, the index in the array changes.
I have tried using $set and manually updating the array... but the index on the array keeps changing.
Here is the model:
const MappedImageSchema = new Schema({
imageUrl: {type: String, required: true},
name: {type: String, required: true},
areas:[
{
name: {type: String},
shape: {type: String},
coords:[{type: Number}],
}
]
});
module.exports = MappedImage = mongoose.model('mappedImages', MappedImageSchema)
Here is the code that performs the update:
// #route POST api/maps/:id/areas
// #desc add an area to a map (by map id)
// #access Private
router.post('/:id/areas/:id_area', passport.authenticate('jwt', { session: false }),
(req, res) => {
MappedImage.findById(req.params.id)
.then(map => {
// get all of the areas from the map...
var allAreas = map.areas;
// now get the index of the area we are going to update
const areaIndex = map.areas.map(item => item._id.toString()).indexOf(req.params.id_area);
// update the information
var coords = req.body.coords.split(',');
const updatedArea = {
name: req.body.name,
shape: req.body.shape,
coords: coords,
};
// set the updated information in the correct map area
allAreas[areaIndex] = updatedArea;
var query = {_id: req.params.id}; // this is the MAP id
var update = {$set: {areas:allAreas}}; // update the areas
var options = {new: true};
MappedImage.findOneAndUpdate(query, update, options)
.then(map => res.json(map))
.catch(err => res.status(404).json({ mapnotfound: err }));
})
.catch(err => res.status(404).json({ mapnotfound: 'Map not found while updating area' }));
}
);
Here is the data BEFORE the call
{
"_id": "5c5c69dda40e872258b4531d",
"imageUrl": "url test",
"name": "name test",
"areas": [
{
"coords": [1,2,3,4,5,6],
"_id": "5c5c8db2f904932dd8d4c560", <---- _id changes every time !
"name": "area name",
"shape": "poly"
}
],
"__v": 3
}
Here is the Postman call I make:
The result of the call is the name gets changed... but so does the index... making the next call fail with "no area found with that index".
What is perplexing about this problem is the _id for the map does not get updated when I run this code:
router.post('/:id', passport.authenticate('jwt', { session: false }),
(req, res) => {
var query = {_id: req.params.id};
var update = {imageUrl: req.body.imageUrl, name: req.body.name};
var options = {new: true};
MappedImage.findOneAndUpdate(query, update, options)
.then(map => res.json(map))
.catch(err => res.status(404).json({ mapnotfound: err }));
});
Update 1
I tried using the areas index and updating just that area... but the _id changes with this code as well:
... same code all the way down to here
allAreas[areaIndex] = updatedArea;
// but instead of calling 'findOneAndUpdate'... call map save
map.save().then(map => res.json(map));
Update 2
I can't get this code to work as areas._id and areas.$ are undefined ?
var query = {_id: req.params.id, areas._id: id_area}; // this is the MAP id
var update = {$set: {areas.$: updatedArea}}; // update the area
Update 3
So, putting the _id in the updatedArea fixes this issue... but it "feels" wrong to do so: ( per eol answer )
const updatedArea = {
_id: req.params.id_area,
name: req.body.name,
shape: req.body.shape,
coords: coords,
};
Update 4
eol - thanks for the verification on the mongoDB side... If that solves the DB id problem... I just need to know why my query is failing. I tried this and all I see in the terminal output is "creating query"... I never see the "query" and it's definition... so something is wrong and I don't know how to figure out what. Here is what I have now:
console.log('creating query');
var query = {"_id": req.params.id, "areas._id": id_area};
console.log('query');
console.log(query);
Update 5
Figured it out why the query not being output, id_area is not defined... but req.params.id_area is !
console.log('creating query');
var query = {"_id": req.params.id, "areas._id": req.params.id_area};
console.log('query');
Update 6
Code is in... but it is still not working. A picture is worth a 1000 words... so here are two:
This one shows the areas ID is still changing:
Here is the code I have now:
console.log('Update area');
console.log('changing area ' + req.params.id_area);
//console.log(req.body);
const { errors, isValid } = mapValidators.validateAreaInput(req.body);
// Check Validation
if(!isValid){
return res.status(400).json(errors);
}
MappedImage.findById(req.params.id)
.then(map => {
// Check to see if area exists
if (
map.areas.filter(
area => area._id.toString() === req.params.id_area
).length === 0
) {
return res.status(404).json({ areanotfound: 'Area does not exist' });
}
console.log('area exists');
// get all of the areas from the map...
var allAreas = map.areas;
console.log('all areas');
console.log(allAreas);
// now get the index of the area we are going to update
const areaIndex = map.areas.map(item => item._id.toString()).indexOf(req.params.id_area);
console.log('area index');
console.log(areaIndex);
// update the information
var coords = req.body.coords.split(',');
const updatedArea = {
name: req.body.name,
shape: req.body.shape,
preFillColor: req.body.preFillColor,
fillColor: req.body.fillColor,
coords: coords,
};
console.log('updated area');
console.log(updatedArea);
// set the updated information in the maps areas
allAreas[areaIndex] = updatedArea;
console.log('creating query');
var query = {"_id": req.params.id, "areas._id": req.params.id_area};
console.log('query');
console.log(query);
var update = {$set: {"areas.$": updatedArea}};
console.log('update');
console.log(update);
var options = {new: true};
MappedImage.findOneAndUpdate(query, update, options)
.then(map => res.json(map))
.catch(err => res.status(404).json({ mapnotfound: err }));
})
.catch(err => res.status(404).json({ mapnotfound: 'Map not found while updating area' }));
Here is the terminal output:
You could try setting the _id property in the updatedArea object with the value of the area that you'd like to update. This would prevent creating a new id while using the $set operator. Something like this:
// now get the index of the area we are going to update
const areaIndex = map.areas.map(item => item._id.toString()).indexOf(req.params.id_area);
// update the information
var coords = req.body.coords.split(',');
const updatedArea = {
_id: id_area,
name: req.body.name,
shape: req.body.shape,
coords: coords,
};
...
Note that with the above solution you're always setting a new array, which is why new id's are generated.
You could also try updating the specific element in the array using the $ operator:
var query = {"_id": req.params.id, "areas._id": id_area}; // this is the MAP id
var update = {$set: {"areas.$": updatedArea}}; // update the area
See the screenshots below for an example (executing the commands in the mongodb-shell) where I'm trying to only update the second array element (i.e. with _id 5c5c8db2f904932dd8d4c561)

Mongodb/mongoose omit a field in response [duplicate]

I have a NodeJS application with Mongoose ODM(Mongoose 3.3.1). I want to retrieve all fields except 1 from my collection.For Example: I have a collection Product Which have 6 fields,I want to select all except a field "Image" . I used "exclude" method, but got error..
This was my code.
var Query = models.Product.find();
Query.exclude('title Image');
if (req.params.id) {
Query.where('_id', req.params.id);
}
Query.exec(function (err, product) {
if (!err) {
return res.send({ 'statusCode': 200, 'statusText': 'OK', 'data': product });
} else {
return res.send(500);
}
});
But this returns error
Express
500 TypeError: Object #<Query> has no method 'exclude'.........
Also I tried, var Query = models.Product.find().exclude('title','Image'); and var Query = models.Product.find({}).exclude('title','Image'); But getting the same error. How to exclude one/(two) particular fields from a collection in Mongoose.
Use query.select for field selection in the current (3.x) Mongoose builds.
Prefix a field name you want to exclude with a -; so in your case:
Query.select('-Image');
Quick aside: in JavaScript, variables starting with a capital letter should be reserved for constructor functions. So consider renaming Query as query in your code.
I don't know where you read about that .exclude function, because I can't find it in any documentation.
But you can exclude fields by using the second parameter of the find method.
Here is an example from the official documentation:
db.inventory.find( { type: 'food' }, { type:0 } )
This operation returns all documents where the value of the type field is food, but does not include the type field in the output.
Model.findOne({ _id: Your Id}, { password: 0, name: 0 }, function(err, user){
// put your code
});
this code worked in my project. Thanks!! have a nice day.
You could do this
const products = await Product.find().select(['-image'])
I am use this with async await
async (req, res) => {
try {
await User.findById(req.user,'name email',(err, user) => {
if(err || !user){
return res.status(404)
} else {
return res.status(200).json({
user,
});
}
});
} catch (error) {
console.log(error);
}
In the updated version of Mongoose you can use it in this way as below to get selected fields.
user.findById({_id: req.body.id}, 'username phno address').then(response => {
res.status(200).json({
result: true,
details: response
});
}).catch(err => {
res.status(500).json({ result: false });
});
I'm working on a feature. I store a userId array name "collectedUser" than who is collected the project. And I just want to return a field "isCollected" instead of "collectedUsers". So select is not what I want. But I got this solution.
This is after I get projects from database, I add "isCollected".
for (const item of projects) {
item.set("isCollected", item.collectedUsers.includes(userId), {
strict: false,
})
}
And this is in Decorator #Schema
#Schema({
timestamps: true,
toObject: {
virtuals: true,
versionKey: false,
transform: (doc, ret, options): Partial<Project> => {
return {
...ret,
projectManagers: undefined,
projectMembers: undefined,
collectedUsers: undefined
}
}
}
})
Finally in my controller
projects = projects.map(i => i.toObject())
It's a strange tricks that set undefined, but it really work.
Btw I'm using nestjs.
You can do it like this
const products = await Product.find().select({
"image": 0
});
For anyone looking for a way to always omit a field - more like a global option rather than doing so in the query e.g. a password field, using a getter that returns undefined also works
{
password: {
type: String,
required: true,
get: () => undefined,
},
}
NB: Getters must be enabled with option { toObject: { getters:true } }
you can exclude the field from the schema definition
by adding the attribute
excludedField : {
...
select: false,
...
}
whenever you want to add it to your result,
add this to your find()
find().select('+excludedFiled')

Exclude fields from result in MongoDB monk

I want to exclude some fields from result.
I have code:
users = db.select('users');
users.find( {}, { sort: { points:1 }, privateKey:0, publicKey:0}, function(err,data){
res.send(data);
});
I want to exclude private and public key from results.
Can I do that using monk?
You can also do it like this:
users.find( {}, { sort: { points:1 }, fields : { privateKey:0, publicKey:0} },
function(err,data){
res.send(data);
}
);
According to documentation first argument in find is filter and second is projection .But you have used sort . It will not able to interpret . You are trying to confuse projection with sort .Sorting should be after find and projection.
You can write projection like { field1: <boolean>, field2: <boolean> ... }
Note :
The find() method always includes the _id field even if the field is not explicitly stated to return in the projection parameter.
users.find({}, { privateKey: 0, publicKey: 0 }).sort({points: 1}).toArray(
function (err, data) {
res.send(data);
});
For me, I need to use the .project() method:
const someFunction = async () => {
const result = await users
.find({}, { sort: { points: 1 })
.project({ privateKey: 0, publicKey: 0});
};
This is what worked for me for excluding the _id field.
const courseChapters = await db
.collection("users")
.find({}, { projection: { _id: 0 } })
.toArray();
So the example in the question would look something like this.
users.find(
{},
{ projection: { points: 1, privateKey: 0, publicKey: 0 } },
function (err, data) {
res.send(data);
}
);
Check out this other answer that says you may need the fields field instead of projection depending upon your driver

Resources