Passing multiple different Mongodb queries to EJS? - mongodb

When a user submits a form on my site, I want to show them three items: a breakfast item, a lunch item, and a dinner item. To do this, I thought I'd have to individually do a db.collection("recipes").findOne, then return the result of that code to a variable I could then pass to EJS using res.render("meal-plan", {breakfast:breakfast});
However, it turns out the variables gathered from the findOne query must be passed to EJS before the findOne query is closed or else you can't access them. Now I'm stuck with something like this:
var breakfast;
MongoClient.connect('mongodb://localhost', function (err, client) {
if (err) throw err;
var db = client.db('food_app');
db.collection("recipes").findOne({ "breakfast" : true}, function(err, result) {
if (err) {
console.log(err);
} else {
console.log(result.title);
breakfast = result;
client.close();
}
res.render("meal-plan.ejs", {breakfast:breakfast});
});
});
This successfully allows me to pass the breakfast variable to EJS. However, I want to have multiple variables to pass (breakfast, lunch, dinner). How can I do this if I can only pass one variable? Is it acceptable to use multiple res.renders in multiple queries so I can copy/paste the MongoClient code three times or put it in a function?

In meal-plan.ejs, you should use forEach
See more at https://ejs.co
<%breakfast.forEach(bf){%>
<p><%bf.name%></p>
<%}%>

Related

mongoose .skip() based on parameter value

I'm trying to implement pagination with mongoose for a mongoDB collection.
The console.log(req.params.start); correctly outputs the start value. However, the query does not start at this value — it starts from the beginning, regardless of the value.
If I change req.params.start to a non variable number, 2, for example — it correctly skips 2 records.
sampleRoute.route('/collection/:start').get((req, res) => {
console.log(req.params.start);
MyModel.find()
.skip(req.params.start)
.limit(2)
.exec(function (err, doc) {
if(err) { res.status(500).json(err); return; };
res.status(200).json(doc);
});
})
How can I successfully skip using a variable? Thank you.
As req.params.start is passed as a string, I needed to convert it to Number:
.skip(Number(req.params.start))

Mongoose - populate return _id only instead of a Object [duplicate]

In Mongoose, I can use a query populate to populate additional fields after a query. I can also populate multiple paths, such as
Person.find({})
.populate('books movie', 'title pages director')
.exec()
However, this would generate a lookup on book gathering the fields for title, pages and director - and also a lookup on movie gathering the fields for title, pages and director as well. What I want is to get title and pages from books only, and director from movie. I could do something like this:
Person.find({})
.populate('books', 'title pages')
.populate('movie', 'director')
.exec()
which gives me the expected result and queries.
But is there any way to have the behavior of the second snippet using a similar "single line" syntax like the first snippet? The reason for that, is that I want to programmatically determine the arguments for the populate function and feed it in. I cannot do that for multiple populate calls.
After looking into the sourcecode of mongoose, I solved this with:
var populateQuery = [{path:'books', select:'title pages'}, {path:'movie', select:'director'}];
Person.find({})
.populate(populateQuery)
.execPopulate()
you can also do something like below:
{path:'user',select:['key1','key2']}
You achieve that by simply passing object or array of objects to populate() method.
const query = [
{
path:'books',
select:'title pages'
},
{
path:'movie',
select:'director'
}
];
const result = await Person.find().populate(query).lean();
Consider that lean() method is optional, it just returns raw json rather than mongoose object and makes code execution a little bit faster! Don't forget to make your function (callback) async!
This is how it's done based on the Mongoose JS documentation http://mongoosejs.com/docs/populate.html
Let's say you have a BookCollection schema which contains users and books
In order to perform a query and get all the BookCollections with its related users and books you would do this
models.BookCollection
.find({})
.populate('user')
.populate('books')
.lean()
.exec(function (err, bookcollection) {
if (err) return console.error(err);
try {
mongoose.connection.close();
res.render('viewbookcollection', { content: bookcollection});
} catch (e) {
console.log("errror getting bookcollection"+e);
}
//Your Schema must include path
let createdData =Person.create(dataYouWant)
await createdData.populate([{path:'books', select:'title pages'},{path:'movie', select:'director'}])

WaterlineJs find() with no criteria and fields/select provided does not work

I am trying to fetch all the records but with selected fields, I have tried the following ways but none works:
Post.find(
{
where: {},
select: ['title']
}
);
Post.find(
{},
{
fields: {
title: 1
}
}
);
As this answer points out, the fields param "WILL work as long as you pass other params with it such as limit or order."
Alternatively, if you want this throughout your application, you could define a custom toJSON function for your model, under attributes. If not, you could still define it under some other (e.g. filter) and use map to return the custom objects instead of the default model. Remember to take care of the control flow while using map though. Use async/promises/raw logic to avoid returning before all objects are processed.
The issue has been resolved in sails-mongo latest version:
https://github.com/balderdashy/waterline/issues/1098
Thanks
I've played with trying to get above answer to use limit or order to kick in the projection to no avail.
I did see this in the docs located here:
http://sailsjs.org/documentation/reference/waterline-orm/models/native
With an out of the box solution for exactly what you're doing (pasted here for ease of use).
Pet.native(function(err, collection) {
if (err) return res.serverError(err);
collection.find({}, {
name: true
}).toArray(function (err, results) {
if (err) return res.serverError(err);
return res.ok(results);
});
});
Swap out the response base things and change Pet to Post and, this ought to work in the sails console:
Post.native(function(err, collection) {
if (err) throw new Error(err);
collection.find({}, {
title: true
}).toArray(function (err, results) {
if (err) throw new Error(err);
console.log(results);
});
});
You'll still get the _id field, and if you don't want that then hit the Mongo docs on not getting those hint(title: true, _id: false)hint
Hope this helps!

Is there a way to query MongoDB Rest API with a list or array of IDs

I'm using a MEAN stack and with Mongoose. Is there a way to query MongoDB with multiple ids to only return those specific IDs in one query e.g. /api/products/5001,5002,5003
Is this possible or would I need to query each product individually or add an additional attribute to the products and query by that.
Update: To clarify as suggested below I've managed to get it partially working using {'_id': { $in: [5001,5002,5003]} however I'm having problems figuring out how to pass the list from the api url to the find function.
Using Express.js for router
router.get('/list/:ids', controller.showByIDs);
exports.showByIDs = function(req, res) {
Product.find({'_id': { $in: [req.params.ids]}}, function (err, product) {
if(err) { return handleError(res, err); }
if(!product) { return res.send(404); }
return res.json(product);
})
};
Then trying /api/products/list/5001 works however /api/products/list/5001,5002 doesn't. I'm not sure if it's a syntax problem in the url or my router code that needs to change or the controller.
You can use the $in operator to query for multiple values at once:
Products.find({_id: {$in: [5001, 5002, 5003]}}, function (err, products) { ... });
On the Express side, you need to use a format for the ids parameter that lets you split it into an array of id values, like you had in your first example:
/api/products/5001,5002,5003
Then in your route handler, you can call the split function on the req.params.ids string to turn it into an array of id values that you can use with $in:
exports.showByIDs = function(req, res) {
var ids = req.params.ids.split(',');
Product.find({'_id': { $in: ids}}, function (err, product) {
if(err) { return handleError(res, err); }
if(!product) { return res.send(404); }
return res.json(product);
})
};

Is data returned from Mongoose immutable?

I want to add to the return data from a mongoose query:
User.findById(userId, function(err, data) {
if (!err) {
data.newvar = 'Hello, world';
}
});
However, when I console log the output, the newvar does not exist. I've also tried this using Underscore's extend:
_.extend(data, {'newvar': 'Hello, world'});
With no luck either. Since I have nested documents, using a shallow copy won't work. Is there any way to append data here?
One way to handle this is to convert your mongoose model instance into a plain object that you have full control over by calling toObject() on it:
User.findById(userId, function(err, data) {
if (!err) {
data = data.toObject();
data.newvar = 'Hello, world';
}
});
If you want a more structured solution, you can add virtual attributes to your schema as described here.
As it turns out, Mongoose documents are their own special class and not standard Javascript objects. In order to get a javascript option that can be extended, you must use the toObject() method.
Now you can use lean() method to return a plain js object:
User.findById(userId)
.lean()
.exec( function(err, data) {
if (!err) {
data.newvar = 'Hello, world';
}
});
Why can't you modify the data returned by a Mongoose Query (ex: findById)