Mongoose not saving to database - node.js

This REST endpoint updates the favourites list in my database, by removing qname from user.favourites.
My problem is that although updatedUser.favourites is correct at the end of the code, this is not actually being persisted in the database (similar code to add a qname on a separate endpoint do work). I'm sure this is a silly mistake, but what I've written feels intuitively correct.
exports.remQname = function (req, res, next) {
var userId = req.user.id;
var qname = req.params.qname;
console.log('addQname %s %s', userId, qname);
User.findOne({
_id: userId
}, function(err, user) {
if (err) return next(err);
if (!user) return res.json(401);
console.log(user);
// if the qname already in list, remove it, otherwise add it
var favourites = user.favourites;
var matches = _.remove(favourites, function (f) {
return f == qname
});
console.log('Matches: %s %s', matches, favourites);
user.favourites = favourites;
user.save(function(err, updatedUser){
if (err) throw err;
console.log(updatedUser); // correct info, but does not reflect database content
res.status(200).send(updatedUser.favourites);
});
});
};
Here is my Schema
var UserSchema = new Schema({
email: String,
password: String,
token: String,
role: {type: String, default: 'user'},
favourites: Array
});
module.exports = mongoose.model('User', UserSchema);

You can directly remove all instances of a specific value from an array field using $pull, so it would be more efficient to let MongoDB do the work rather than trying to manipulate the array yourself.
User.update({_id: userId},
{$pull: {favourites: qname}},
function(err, numberAffected, raw) { ... });
I'd also suggest changing your definition of favourites in the schema to be [String] instead of just Array if it does contain an array of strings.

It had something to do with lodash - this works instead
var newFavs = _.reject(user.favourites, function (f) {
return f == qname
});
console.log('Favourites - Old: %s New: %s', user.favourites, newFavs);
// delete user.favourites;
user.favourites = newFavs;

Related

Mongoose can't search by number field

I have a schema that has an id field that is set to a string. When I use collection.find({id: somenumber}) it returns nothing.
I've tried casting somenumber to a string and to a number. I've tried sending somenumber through as a regex. I've tried putting id in quotes and bare... I have no idea what's going on. Any help and input would be appreciated.
Toys.js
var Schema = mongoose.Schema;
var toySchema = new Schema( {
id: {type: String, required: true, unique: true},
name: {type: String, required: true},
price: Number
} );
My index.js is as such
app.use('/findToy', (req, res) => {
let query = {};
if (req.query.id)
query.id = req.query.id;
console.log(query);
// I've tried using the query variable and explicitly stating the object as below. Neither works.
Toy.find({id: '123'}, (err, toy) => {
if (!err) {
console.log("i'm right here, no errors and nothing in the query");
res.json(toy);
}
else {
console.log(err);
res.json({})
}
})
I know that there is a Toy in my mongoDB instance with id: '123'. If I do Toy.find() it returns:
[{"_id":"5bb7d8e4a620efb05cb407d2","id":"123","name":"Dog chew toy","price":10.99},
{"_id":"5bb7d8f7a620efb05cb407d3","id":"456","name":"Dog pillow","price":25.99}]
I'm at a complete loss, really.
This is what you're looking for. Visit the link for references, but here's a little snippet.
For the sake of this example, let's have a static id, even though Mongo creates a dynamic one [ _id ]. Maybe that what is the problem here. If you already a record in your DB with that id, there's no need for adding it manually, especially not the already existing one. Anyways, Drop your DB collection, and try out this simple example:
// Search by ObjectId
const id = "123";
ToyModel.findById(id, (err, user) => {
if(err) {
// Handle your error here
} else {
// If that 'toy' was found do whatever you want with it :)
}
});
Also, a very similar API is findOne.
ToyModel.findOne({_id: id}, function (err, toy) { ... });

nodejs app mongoose database where clause with join

I have a schema article defined as:
var ArticleSchema = new Schema({
title: String,
content: String,
creator: {
type: Schema.ObjectId,
ref: 'User'
}
})
And user schema:
var UserSchema = new Schema({
type: String, //editor, admin, normal
username: String,
password: String,
})
I need to query all the article created by editor, i.e. in sql language
select Article.title as title, Article.content as content
from Article inner join User
on Article.creator = User._id
where User.type = 'editor'
This is what I have tried
exports.listArticle = function(req, res, next) {
var creatorType = req.query.creatorType
var criteria = {}
if (creatorType)
criteria = {'creator.type': creatorType}
Article.find(criteria).populate('creator').exec(function(err, articles) {
if (err)
return next(err)
//ok to send the array of mongoose model, will be stringified, each toJSON is called
return res.json(articles)
})
}
The returned articles is an empty array []
I also tried Article.populate('creator').find(criteria), also not working with error:
utils.populate: invalid path. Expected string. Got typeof `undefined`
There is no concept of joins in MongoDB, as it is a not a relational database.
The populate method is actually a feature of Mongoose and internally uses multiple queries to replace the referred field.
This will have to be done using a multi-part query, first on the User collection, then on the Article collection.
exports.listArticle = function(req, res, next) {
var creatorType = req.query.creatorType
var criteria = {}
if (creatorType)
criteria = {'type': creatorType}
User.distinct('_id', criteria, function (err, userIds) {
if (err) return next(err);
Article.find({creator: {$in: userIds}}).populate('creator').exec(function(err, articles) {
if (err)
return next(err)
//ok to send the array of mongoose model, will be stringified, each toJSON is called
return res.json(articles)
})
})
}

Check if ID exists in a collection with mongoose

For instance, I have a collection User:
var mongoose = require('mongoose');
var UserSchema = new mongoose.Schema({
email: String,
googleId: String,
facebookId: String,
displayName: String,
active: Boolean
});
module.exports = mongoose.model('User', UserSchema);
And then I have an ID:
var userID = "some-user-id"
What is the right way to just check if this id exists in the User collection. I don't need it to read the file or return it, I just need the true or false value.
Here is one way to achieve it:
User.findOne({
_id: userID
}, function (err, existingUser) {
But is there faster and more efficient way?
Use count rather than findOne.
This will (under the hood) cause mongoose to use find : http://docs.mongodb.org/manual/reference/method/db.collection.count
findOne() will read + return the document if it exists
On the other hand, find() just returns a cursor (or not) and only reads the data if you iterate over the cursor.
So in our case, we're not iterating over the cursor, merely counting the results returned.
User.countDocuments({_id: userID}, function (err, count){
if(count>0){
//document exists });
}
});
You can now use User.exists() as of September 2019 like so:
const doesUserExit = await User.exists({ _id: userID });
From the docs:
Under the hood, MyModel.exists({ answer: 42 }) is equivalent to
MyModel.findOne({ answer: 42 }).select({ _id: 1 }).lean().then(doc =>
!!doc)
The accepted answer is fine for small collections.
A faster way on larger collections is to simply use this:
const result = await User.findOne({ _id: userID }).select("_id").lean();
if (result) {
// user exists...
}
// or without "async/await":
User.findOne({ _id: userID }).select("_id").lean().then(result => {
if (result) {
// user exists...
}
});
It won't return all fields. I believe they are currently working on a new feature to support what you (and I) want.
In the meantime you could create a plugin, very simple and reusable.
Create an any.js file with this code:
module.exports = function any(schema, options) {
schema.statics.any = async function (query) {
const result = await this.findOne(query).select("_id").lean();
return result ? true : false;
};
}
Then in your model you do this:
var mongoose = require('mongoose');
const any = require('./plugins/any'); // I'm assuming you created a "plugins" folder for it
var UserSchema = new mongoose.Schema({
email: String,
googleId: String,
facebookId: String,
displayName: String,
active: Boolean
});
UserSchema.plugin(any);
module.exports = mongoose.model('User', UserSchema);
...and use it like this:
const result = await User.any({ _id: userID });
if (result) {
// user exists...
}
// or without using "async/await":
User.any({ _id: userID }).then(result => {
if (result) {
// user exists...
}
});
OR you can simply use exists function, without making any async/await:
myData = {_id: userID};
User.exists(myData,(error, result)=>{
if (error){
console.log(error)
} else {
console.log("result:", result) //result is true if myData already exists
}
});
You can play with the result now!
User.exists({ _id: userID }).then(exists => {
if (exists) {
res.redirect('/dashboard')
} else {
res.redirect('/login')
}
})
More info can be found at Mongoose docs.
The accepted answer is excellent, but I would really recommend using estimatedDocumentCount() if you are searching existing document by an indexed property (like _id of X).
On the other hand, this should actually work better and is cleaner.

How to check values against the DB when using the pre-save hook?

On a User schema, I'd like to check if the specified email already exists for the specified shop, before saving.
var UserSchema = new Schema({
_shop: {
type: Schema.Types.ObjectId,
ref: 'Shop',
required: true
},
email: String,
//...
});
UserSchema.pre('save', function(next) {
if (!this.isNew) return next();
// How to do use the static method isThatEmailFreeForThisShop here?
});
UserSchema.statics.isThatEmailFreeForThisShop = function(email, shop_id, cb) {
this.find({email: email, _shop: shop_id}, function(err, users) {
// ...
});
});
There could be different users with the same email as long as they are from different shops.
I do not know how to use the static method in the pre-save hook...
Thanks!
You've created a User Model instance somewhere (I'll call it User):
var User = mongoose.model('user', UserSchema);
So, the isThatEmailFreeForThisShop function is available on the User model:
User.isThatEmailFreeForThisShop(...)
From your save hook:
UserSchema.pre('save', function(next) {
if (!this.isNew) return next();
User.isThatEmailFreeForThisShop(this.email, this._shop,
function(err, result) {
if (result) { // found
// do something
return next({ error: "duplicate found" });
}
return next();
});
});
You may also want to switch to using the pre-validate rather than save.
I'd expect in your function, isThatEmailFreeForThisShop that you'd call the cb parameter when the results have been "found".
You probably would use findOne (reference) rather than find. Given that there's still a race condition, you'd want to add an index as a compound index email and shop_id and set the unique attribute to true to prevent duplicates from sneaking in (then, you'll need to handle the fact that a save on a model instance may throw an error.)
UserSchema.statics.isThatEmailFreeForThisShop = function(email, shop_id, cb) {
this.findOne({email: email, _shop: shop_id}, function(err, user) {
// ...
cb(err, user != null);
});
});

How to Make Mongoose's .populate() work with an embedded schema/subdocument?

I read up that you can make Mongoose auto pouplate ObjectId fields. However I am having trouble structuring a query to populate fields in a subdoc.
My models:
var QuestionSchema = new Schema({
question_text: String,
type: String,
comment_field: Boolean,
core_question: Boolean,
identifier: String
});
var SurveyQuestionSchema = new Schema({
question_number: Number,
question: {type: Schema.Types.ObjectId, ref: 'Question', required: true} //want this popuplated
});
var SurveySchema = new Schema({
start_date: Date,
end_date: Date,
title: String,
survey_questions: [SurveyQuestionSchema]
});
Right now I achieve the effect by doing:
Survey.findById(req.params.id, function(err, data){
if(err || !data) { return handleError(err, res, data); }
var len = data.survey_questions.length;
var counter = 0;
var data = data.toJSON();
_.each(data.survey_questions, function(sq){
Question.findById(sq.question, function(err, q){
sq.question = q;
if(++counter == len) {
res.send(data);
}
});
});
});
Which obviously is a very error-prone way of doing it...
As I noted in the comments above, this is an issue currently under scrutiny by the mongoose team (not yet implemented).
Also, looking at your problem from an outsider's perpsective, my first thought would be to change the schema to eliminate SurveyQuestion, as it has a very relational db "join" model feel. Mongoose embedded collections have a static sort order, eliminating the need for keeping a positional field, and if you could handle question options on the Survey itself, it would reduce the schema complexity so you wouldn't need to do the double-populate.
That being said, you could probably reduce the queries down to 2, by querying for all the questions at once, something like:
Survey.findById(req.params.id, function(err, data){
if(err || !data) { return handleError(err, res, data); }
var data = data.toJSON();
var ids = _.pluck(data.survey_questions, 'question');
Question.find({_id: { $in: ids } }, function(err, questions) {
_.each(data.survey_questions, function(sq) {
sq.question = _.find(questions, function(q) {
var id = q._id.toString();
return id == sq.question;
});
});
res.send(data);
});
});

Resources