Mongoose display comments and stars(likes) for each post [duplicate] - node.js

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'}])

Related

Delete from mongoDB by id

I would like to delete an item from my database using the id I passed to it, not by the object ID. How do I do this?
I have done some reading on deleteOne but I'm not quite sure how to go about putting it into use.
Each movie has a button to delete a movie so I need the id of each movie to be passed into the remove function. New to MERN stack here.
{
"_id": "5fa55741aae528142e96c9e6",
"id": 531219,
"user": "5fa406cf937ce199eeb176a1",
"title": "Roald Dahl's The Witches",
"overview": "large string ... ",
"poster_path": "/betExZlgK0l7CZ9CsCBVcwO1OjL.jpg",
"vote_average": "7.1",
"__v": 0
},
My code so far
router.delete('/:id/delete', auth, async (req, res)=>{
const movie = await Movie.findByIdAndRemove(req.body.id)
if(!movie){
res.send('Movie not found')
} else{
movie.remove()
res.send('movie deleted')
}
})
case REMOVE_MOVIE:
return{...state, wishlist:state.wishlist.filter(movie => movie.id !== payload)}
export const removeMovie = (id) => async dispatch => {
try {
await axios.delete(`/wishlist/${id}/delete`)
dispatch({
type: REMOVE_MOVIE,
payload:id
})
} catch (error) {
console.log('unable to delete movie')
console.error(error)
}
}
Your problem is findByIdAndRemove() looks for the _id and you want to use other value to execute the query. So you can use findOneAndDelete() function from Mongoose.
There are also more functios to do the job as deleteOne or findAndDelete. But if you want to delete by id I recommend use findOneAndDelete because return the deleted document.
Then you have to pass the id to query like this:
var deleted = await model.findAndDelete({id: "myID"})
Note that id is not the same as _id.
And that's all...
From documentation, to know how functions works:
findOneAndDelete()
Deletes a single document based on the filter and sort criteria, returning the deleted document.
deleteOne
Removes a single document from a collection.
And also, if you are using mongoose and you want delete by _id you can use findByIdAndDelete() function instead of findByIdAndRemove().
According to documentation
This function differs slightly from Model.findOneAndRemove() in that findOneAndRemove() becomes a MongoDB findAndModify() command, as opposed to a findOneAndDelete() command. For most mongoose use cases, this distinction is purely pedantic. You should use findOneAndDelete() unless you have a good reason not to.
I was able to remedy this issue using the advice above, but I needed to capture and assign the value of the movie being clicked on to the id value passed into findOneAndDelete. I did this by passing in req.params.id. So it was findOneAndDelete({id: req.params.id}). Now everything works as expected. Thanks!

findOne - 'Specify the Fields to Return' does not work? [duplicate]

I'm pretty new with mongo and nodejs
I've a json as result of my query and I simply want to return the result as an http request, as following:
app.get('/itesms', function(req, res) {
items.find().toArray(function (err, array) {
res.send(array);
})
});
It works, only problem is that I want to hide the _id fields (recursively) from the result.
Any suggestion to do that in an elegant way?
Try this solution:
app.get('/itesms', function(req, res) {
items.find({}, { _id: 0 }).toArray(function (err, array) {
res.send(array);
})
});
The usual .find({}, {_id:0}) approach wasn't working for me, so I went hunting and found in another SO answer that in version 3 of the Mongo API, you need to write it like this: .find({}, {projection:{_id:0}}). So, for example:
let docs = await db.collection("mycol").find({}, {projection:{_id:0}}).toArray();
It seems that (in the nodejs API, at least) you can also write it like this:
let docs = await db.collection("mycol").find({}).project({_id:0}).toArray();
The problem is that you can't project inclusions and exclusions, ie you can't run a query with a 'project' statement that declares what should be included in the response as well as what must be excluded from the response.
From MongoDB documentation:
A projection cannot contain both include and exclude specifications, except for the exclusion of the _id field. In projections that explicitly include fields, the _id field is the only field that you can explicitly exclude.
The way I handled this problem was to go to the end of the process, right before returning the response:
const dbObjectJSON = dbObject.toJson();
delete dbObjectJSON._id;
delete dbObjectJSON.__v;
...
response.json(dbObjectJSON);
Hope this helps.

sorting alpha with mongoose

I'm trying to sort via mongoose 3.6.20 and I am receiving some unexpected results.
I have a list of companies with a name. At first I thought that maybe it was sorting in a case sensitive way. Which based on articles, I expect was true.
I'm now using a virtual property to down case the sort field. However, I'm still getting unexpected results.
CompanySchema.virtual('name_lower').get(function(){
return this.name.toLowerCase();
});
and when I sort
Company.find().sort({ name_lower: 1 });
I'm getting it in the following order:
company name
google
company name (yes a duplicate for testing)
I'm also outputting the value of my virtual property and it looks right. There is no whitespace or funky characters that would result in the 2nd 'company name' from appearing after google.
Using nodejs, express, mongoose.
What am I missing or doing incorrectly?
Update:
Based on the information provided in the answers, I refactored my schema to include some normalized fields and hooked into the pre save event of my document, where I update those normalized fields and sort using them in all future queries.
CompanySchema.pre('save', function(next){
this.normalized_name = this.name;
});
Next, is in the schema I use:
var CompanySchema = mongoose.Schema({
...
normalized_name: { type: String, set: normalize },
...
});
Where normalize is a function that for now, returns a lowercase version of the value passed into it. However, this allows me to expand on it later really fast, and I can quickly do the same to other fields that I might need to sort against.
As of MongoDB v3.4, case insensitive sorting can be done using collation method:
Company.find()
.collation({locale: "en" }) //or whatever collation you want
.sort({name:'asc'})
.exec(function(err, results) {
// use your case insensitive sorted results
});
Unfortunately MongoDB and Mongoose does not currently support complex sorting, so there are 2 options:
As you said, create a new field with the names sanitized to be all lowercase
Run a big for loop over all the data and update each company name to it's lower case form:
db.CompanyCollection.find().forEach(
function(e) {
e.CompanyName = e.CompanyName.toLowerCase();
db.CompanyCollection.save(e);
}
)
or
db.CompanyCollection.update({_id: e._id}, {$set: {CompanyName: e.CompanyName.toLowerCase()
Please see Update MongoDB collection using $toLower and Mongoose: Sort alphabetically as well for more info.
I want to put out that in this hook:
CompanySchema.pre('save', function(next){
this.normalized_name = this.name;
});
You'll have to call next(); at the end, if you want the normalized_name to be saved in the database, so the pre save hook would look like:
CompanySchema.pre('save', function(next){
this.normalized_name = this.name;
next();
});
This answer seems to be more helpful to me. I had to consider diacritics along with the case so I had used strength:3.
Mongoose: Sort alphabetically

How to get all count of mongoose model?

How can I know the count of a model that data has been saved? there is a method of Model.count(), but it doesn't seem to work.
var db = mongoose.connect('mongodb://localhost/myApp');
var userSchema = new Schema({name:String,password:String});
userModel =db.model('UserList',userSchema);
var userCount = userModel.count('name');
userCount is an Object, which method called can get a real count?
Thanks
The reason your code doesn't work is because the count function is asynchronous, it doesn't synchronously return a value.
Here's an example of usage:
userModel.count({}, function( err, count){
console.log( "Number of users:", count );
})
The code below works. Note the use of countDocuments.
var mongoose = require('mongoose');
var db = mongoose.connect('mongodb://localhost/myApp');
var userSchema = new mongoose.Schema({name:String,password:String});
var userModel =db.model('userlists',userSchema);
var anand = new userModel({ name: 'anand', password: 'abcd'});
anand.save(function (err, docs) {
if (err) {
console.log('Error');
} else {
userModel.countDocuments({name: 'anand'}, function(err, c) {
console.log('Count is ' + c);
});
}
});
You should give an object as argument
userModel.countDocuments({name: "sam"});
or
userModel.countDocuments({name: "sam"}).exec(); //if you are using promise
or
userModel.countDocuments({}); // if you want to get all counts irrespective of the fields
For the older versions of mongoose, use
userModel.count({name: "sam"});
The collection.count is deprecated, and will be removed in a future version. Use collection.countDocuments or collection.estimatedDocumentCount instead.
userModel.countDocuments(query).exec((err, count) => {
if (err) {
res.send(err);
return;
}
res.json({ count: count });
});
Background for the solution
As stated in the mongoose documentation and in the answer by Benjamin, the method Model.count() is deprecated. Instead of using count(), the alternatives are the following:
Model.countDocuments(filterObject, callback)
Counts how many documents match the filter in a collection. Passing an empty object {} as filter executes a full collection scan. If the collection is large, the following method might be used.
Model.estimatedDocumentCount()
This model method estimates the number of documents in the MongoDB collection. This method is faster than the previous countDocuments(), because it uses collection metadata instead of going through the entire collection. However, as the method name suggests, and depending on db configuration, the result is an estimate as the metadata might not reflect the actual count of documents in a collection at the method execution moment.
Both methods return a mongoose query object, which can be executed in one of the following two ways. Use .exec() if you want to execute a query at a later time.
The solution
Option 1: Pass a callback function
For example, count all documents in a collection using .countDocuments():
someModel.countDocuments({}, function(err, docCount) {
if (err) { return handleError(err) } //handle possible errors
console.log(docCount)
//and do some other fancy stuff
})
Or, count all documents in a collection having a certain name using .countDocuments():
someModel.countDocuments({ name: 'Snow' }, function(err, docCount) {
//see other example
}
Option 2: Use .then()
A mongoose query has .then() so it’s “thenable”. This is for a convenience and query itself is not a promise.
For example, count all documents in a collection using .estimatedDocumentCount():
someModel
.estimatedDocumentCount()
.then(docCount => {
console.log(docCount)
//and do one super neat trick
})
.catch(err => {
//handle possible errors
})
Option 3: Use async/await
When using async/await approach, the recommended way is to use it with .exec() as it provides better stack traces.
const docCount = await someModel.countDocuments({}).exec();
Learning by stackoverflowing,
Using mongoose.js you can count documents,
count all
const count = await Schema.countDocuments();
count specific
const count = await Schema.countDocuments({ key: value });
The highest voted answers here are perfectly fine I just want to add up the use of await so that the functionality asked for can be achieved:
const documentCount = await userModel.count({});
console.log( "Number of users:", documentCount );
It's recommended to use countDocuments() over 'count()' as it will be deprecated going on. So, for now, the perfect code would be:
const documentCount = await userModel.countDocuments({});
console.log( "Number of users:", documentCount );
Model.count() method is deprecated in mongoose version 6.2.0. If you want to count the number of documents in a collection, e.g. count({}), use the estimatedDocumentCount() function instead. Otherwise, use the countDocuments() function instead.
Model.estimatedDocumentCount() Estimates the number of documents in the MongoDB collection. It is Faster than using countDocuments() for large collections because estimatedDocumentCount() uses collection metadata rather than scanning the entire collection.
Example:
const numAdventures = await Adventure.estimatedDocumentCount();
reference : https://mongoosejs.com/docs/api.html#model_Model.estimatedDocumentCount
As said before, your code will not work the way it is. A solution to that would be using a callback function, but if you think it would carry you to a 'Callback hell', you can search for "Promisses".
A possible solution using a callback function:
//DECLARE numberofDocs OUT OF FUNCTIONS
var numberofDocs;
userModel.count({}, setNumberofDocuments); //this search all DOcuments in a Collection
if you want to search the number of documents based on a query, you can do this:
userModel.count({yourQueryGoesHere}, setNumberofDocuments);
setNumberofDocuments is a separeted function :
var setNumberofDocuments = function(err, count){
if(err) return handleError(err);
numberofDocs = count;
};
Now you can get the number of Documents anywhere with a getFunction:
function getNumberofDocs(){
return numberofDocs;
}
var number = getNumberofDocs();
In addition , you use this asynchronous function inside a synchronous one by using a callback, example:
function calculateNumberOfDoc(someParameter, setNumberofDocuments){
userModel.count({}, setNumberofDocuments); //this search all DOcuments in a Collection
setNumberofDocuments(true);
}

remove _id from mongo result

I'm pretty new with mongo and nodejs
I've a json as result of my query and I simply want to return the result as an http request, as following:
app.get('/itesms', function(req, res) {
items.find().toArray(function (err, array) {
res.send(array);
})
});
It works, only problem is that I want to hide the _id fields (recursively) from the result.
Any suggestion to do that in an elegant way?
Try this solution:
app.get('/itesms', function(req, res) {
items.find({}, { _id: 0 }).toArray(function (err, array) {
res.send(array);
})
});
The usual .find({}, {_id:0}) approach wasn't working for me, so I went hunting and found in another SO answer that in version 3 of the Mongo API, you need to write it like this: .find({}, {projection:{_id:0}}). So, for example:
let docs = await db.collection("mycol").find({}, {projection:{_id:0}}).toArray();
It seems that (in the nodejs API, at least) you can also write it like this:
let docs = await db.collection("mycol").find({}).project({_id:0}).toArray();
The problem is that you can't project inclusions and exclusions, ie you can't run a query with a 'project' statement that declares what should be included in the response as well as what must be excluded from the response.
From MongoDB documentation:
A projection cannot contain both include and exclude specifications, except for the exclusion of the _id field. In projections that explicitly include fields, the _id field is the only field that you can explicitly exclude.
The way I handled this problem was to go to the end of the process, right before returning the response:
const dbObjectJSON = dbObject.toJson();
delete dbObjectJSON._id;
delete dbObjectJSON.__v;
...
response.json(dbObjectJSON);
Hope this helps.

Resources