Mongoose: Find, modify, save - javascript

I have a Mongoose User model:
var User = mongoose.model('Users',
mongoose.Schema({
username: 'string',
password: 'string',
rights: 'string'
})
);
I want to find one instance of the User model, modify it's properties, and save the changes. This is what I have tried (it's wrong!):
User.find({username: oldUsername}, function (err, user) {
user.username = newUser.username;
user.password = newUser.password;
user.rights = newUser.rights;
user.save(function (err) {
if(err) {
console.error('ERROR!');
}
});
});
What is the syntax to find, modify and save an instance of the User model?

The user parameter of your callback is an array with find. Use findOne instead of find when querying for a single instance.
User.findOne({username: oldUsername}, function (err, user) {
user.username = newUser.username;
user.password = newUser.password;
user.rights = newUser.rights;
user.save(function (err) {
if(err) {
console.error('ERROR!');
}
});
});

Why not use Model.update? After all you're not using the found user for anything else than to update it's properties:
User.update({username: oldUsername}, {
username: newUser.username,
password: newUser.password,
rights: newUser.rights
}, function(err, numberAffected, rawResponse) {
//handle it
})

findOne, modify fields & save
User.findOne({username: oldUsername})
.then(user => {
user.username = newUser.username;
user.password = newUser.password;
user.rights = newUser.rights;
user.markModified('username');
user.markModified('password');
user.markModified('rights');
user.save(err => console.log(err));
});
OR findOneAndUpdate
User.findOneAndUpdate({username: oldUsername}, {$set: { username: newUser.username, user: newUser.password, user:newUser.rights;}}, {new: true}, (err, doc) => {
if (err) {
console.log("Something wrong when updating data!");
}
console.log(doc);
});
Also see updateOne

I wanted to add something very important. I use JohnnyHK method a lot but I noticed sometimes the changes didn't persist to the database. When I used .markModified it worked.
User.findOne({username: oldUsername}, function (err, user) {
user.username = newUser.username;
user.password = newUser.password;
user.rights = newUser.rights;
user.markModified(username)
user.markModified(password)
user.markModified(rights)
user.save(function (err) {
if(err) {
console.error('ERROR!');
}
});
});
tell mongoose about the change with doc.markModified('pathToYourDate') before saving.

If you want to use find, like I would for any validation you want to do on the client side.
find returns an ARRAY of objects
findOne returns only an object
Adding user = user[0] made the save method work for me.
Here is where you put it.
User.find({username: oldUsername}, function (err, user) {
user = user[0];
user.username = newUser.username;
user.password = newUser.password;
user.rights = newUser.rights;
user.save(function (err) {
if(err) {
console.error('ERROR!');
}
});
});

You could also write it a little more cleaner using updateOne & $set, plus async/await.
const updateUser = async (newUser) => {
try {
await User.updateOne({ username: oldUsername }, {
$set: {
username: newUser.username,
password: newUser.password,
rights: newUser.rights
}
})
} catch (err) {
console.log(err)
}
}
Since you don't need the resulting document, you can just use updateOne instead of findOneAndUpdate.
Here's a good discussion about the difference: MongoDB 3.2 - Use cases for updateOne over findOneAndUpdate

Related

Using Multiple FindOne in Mongodb

I am trying to extend the amount of fields that our API is returning. Right now the API is returning the student info by using find, as well as adding some information of the projects by getting the student info and using findOne to get the info about the project that the student is currently registered to.
I am trying to add some information about the course by using the same logic that I used to get the project information.
So I used the same findOne function that I was using for Projects and my logic is the following.
I created a variable where I can save the courseID and then I will put the contents of that variable in the temp object that sending in a json file.
If I comment out the what I added, the code works perfectly and it returns all the students that I require. However, when I make the additional findOne to get information about the course, it stops returning anything but "{}"
I am going to put a comment on the lines of code that I added, to make it easier to find.
Any sort of help will be highly appreciated!
User.find({
isEnrolled: true,
course: {
$ne: null
}
},
'email pantherID firstName lastName project course',
function(err, users) {
console.log("err, users", err, users);
if (err) {
return res.send(err);
} else if (users) {
var userPromises = [];
users.map(function(user) {
userPromises.push(new Promise(function(resolve, reject) {
///////// Added Code START///////
var courseID;
Course.findOne({
fullName: user.course
}, function(err, course) {
console.log("err, course", err, course);
if (err) {
reject('')
}
courseID = course ? course._id : null
//console.log(tempObj)
resolve(tempObj)
}),
///// ADDED CODE END //////
Project.findOne({
title: user.project
}, function(err, proj) {
console.log("err, proj", err, proj);
if (err) {
reject('')
}
//Course ID, Semester, Semester ID
//map to custom object for MJ
var tempObj = {
email: user.email,
id: user.pantherID,
firstName: user.firstName,
lastName: user.lastName,
middle: null,
valid: true,
projectTitle: user.project,
projectId: proj ? proj._id : null,
course: user.course,
courseId: courseID
}
//console.log(tempObj)
resolve(tempObj)
})
}))
})
//async wait and set
Promise.all(userPromises).then(function(results) {
res.json(results)
}).catch(function(err) {
res.send(err)
})
}
})
using promise could be bit tedious, try using async, this is how i would have done it.
// Make sure User, Course & Project models are required.
const async = require('async');
let getUsers = (cb) => {
Users.find({
isEnrolled: true,
course: {
$ne: null
}
}, 'email pantherID firstName lastName project course', (err, users) => {
if (!err) {
cb(null, users);
} else {
cb(err);
}
});
};
let findCourse = (users, cb) => {
async.each(users, (user, ecb) => {
Project.findOne({title: user.project})
.exec((err, project) => {
if (!err) {
users[users.indexOf(user)].projectId = project._id;
ecb();
} else {
ecb(err);
}
});
}, (err) => {
if (!err) {
cb(null, users);
} else {
cb(err);
}
});
};
let findProject = (users, cb) => {
async.each(users, (user, ecb) => {
Course.findOne({fullName: user.course})
.exec((err, course) => {
if (!err) {
users[users.indexOf(user)].courseId = course._id;
ecb();
} else {
ecb(err);
}
});
}, (err) => {
if (!err) {
cb(null, users);
} else {
cb(err);
}
});
};
// This part of the code belongs at the route scope
async.waterfall([
getUsers,
findCourse,
findProject
], (err, result) => {
if (!err) {
res.send(result);
} else {
return res.send(err);
}
});
Hope this gives better insight on how you could go about with multiple IO transactions on the same request.

Getting a variable to outer scope

I'm confused by this one. First of all this is my code:
router.post('/update', (req, res, next) => {
// Todo legit credit card holding
Account.findOneAndUpdate(
{ _id: req.user._id },
{
$set: {
// username: req.body.username,
creditCardNo: req.body.cardNo,
isPremium: true,
},
},
{ upsert: true },
(err, doc) => {
if (err) {
console.log(err);
}
}
);
var newUser;
Account.findById(req.user._id,(err, doc)=>{
if(err){
console.log(err);
}
else{
newUser = doc;
}
});
console.log(newUser);
res.render('user-pannel/pannel', {
title: 'User pannel',
user: newUser,
});
});
What it does is: It gets the POST call and updates a record in the db. Now I want to basically reload the the page (res.render part) and send the new user object.
I need to send the new one, because the one in req.user is now outdated (was updated before and I'm just printing the old version).
I tried getting around the problem by doing this newUser = doc;, but for some reason the newUservariable is undefined when logged outside of the findById method. Why? If I console log the doc inside of the findById method, it returns the changed object.
I turned it into an async function and awaited its resolution with the desired value.
router.post('/update', async (req, res, next) => {
// Todo legit credit card holding
Account.findOneAndUpdate(
{ _id: req.user._id },
{
$set: {
// username: req.body.username,
creditCardNo: req.body.cardNo,
isPremium: true,
},
},
{ upsert: true },
(err, doc) => {
if (err) {
console.log(err);
}
}
);
const newUser = await new Promise((resolve, reject) => {
Account.findById(req.user._id,(err, doc) => {
if(err) reject(err);
else resolve(doc);
});
});
console.log(newUser);
res.render('user-pannel/pannel', {
title: 'User pannel',
user: newUser,
});
});

Nodejs, bcrypt async, mongoose login

I have the following code for the user model
const userSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
username: {
type: String,
required: true,
trim: true,
minlength: 3,
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
}
})
Then for hashing the password i have this code
UserSchema.pre('save', function (next) {
if (this.isModified('password')){
bcrypt.genSalt(10, (err, salt)=>{
bcrypt.hash(this.password, salt, (err, hash)=>{
this.password = hash;
next();
})
});
}
next();
})
Then for checking the hash against the input value from user i have the following code
userSchema.statics.comparePassword = function(password){
let user = this;
return bcrypt.compareAsync(password, user.password)
}
So when it comes to the usage of all of these pieces of code i have the following
async loginUser(req, res) {
try{
const {email, password} = req.body
const user = await User.findOne({
email: req.body.email
})
if(!user){
return res.status(403).send({
error: "Incorrect details email"
})
}
const isPassValid = await user.comparePassword(password)
}catch(err){
res.status(403).send({
error: "The bid deal happened"
})
}
}
So I've tried searching on google and on this forum to find the answers but everything seems to be outdated or not working particularly for my situation. This code always sends "The bid deal happened", i've tried to debug it from all sides but its still unsuccessful.
The question is how to make it work? so that i can compare the passwords in the right way
P.S I've tried changing from compareAsync to compareSync, shows no effect
You encrypt the password when the value is changed, but not when you insert a new mongo document, you can check this with document.isNew.
I have updated your save method to the follow.
UsersSchema.pre('save', function (next) {
let user = this;
if (this.isModified('password') || this.isNew) {
bcrypt.genSalt(10, (err, salt) => {
if (err) {
return next(err);
}
bcrypt.hash(user.password, salt, (err, hash) => {
if (err) {
return next(err);
}
user.password = hash;
next();
});
});
} else {
next();
}
});
Also, Schema.statics is used to serve static methods. The this context will not return the user, thus making this.password undefined. To populate the instances of your schema with methods, you have to append them to the Schema.methods object.
I have used bcrypt.compare in the past, I dont know if bcrypt.compareAsync is a valid method because the first one is already async. And if it was async, it wouldnt directly return a value. Compare requires a callback.
UsersSchema.methods.comparePassword = function (password, callback) {
bcrypt.compare(password, this.password, (err, isMatch) => callback(err, isMatch));
};
To compare the password, u can do something like the following:
const { email, password } = req.body
User.findOne({
email: email,
}, (err, user) => {
if (err) throw err;
if (user) {
user.comparePassword(password, (err, match) => {
if (match && !err) {
// match
}
});
}
});

Mongoose | Object changes in pre-save hook are not saved to db

I've run into a problem, that I'm not able to solve.
I'll try to describe it as meaningful and simple as possible.
This is my method, that handles post request and saves data:
app.post('/users/', (req, res) => {
let body = _.pick(req.body, ["email", "password"]);
let user = new User(body);
user.save().then(
user => res.json(user),
err => res.send(err)
)
});
When I save new user to database, this pre-save hook fires:
userSchema.pre('save', function(next) {
var user = this;
if(user.isNew){
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(user.password, salt, (err, hash) => {
user.password = hash;
console.log(user);
next();
})
})
}
next();
})
For this input in POST body:
{
"email": "example#example.com",
"password": "somepass"
}
console.log from pre-save hook logs:
{ __v: 0,
email: 'example#example.com',
password: '$2a$10$tWuuvw.wGicr/BTzHaa7k.TdyZRc5ADDV0X1aKnItvVm6JYVe5dsa',
_id: 59482e8136fd8d2bf41e24b7
}
however in db I've got:
{
"_id" : ObjectId("59482e8136fd8d2bf41e24b7"),
"email" : "example#example.com",
"password" : "somepass",
"__v" : 0
}
Clearly changes on user object are not saved and in save() method I still use old values with unhashed password. Why is that? And how can I make changes from pre-save hook to be stored?
The problem is that you're always calling next() after the if block, even when the password needs async encrypting.
Change your code to only do that for existing user docs:
if(user.isNew){
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(user.password, salt, (err, hash) => {
user.password = hash;
console.log(user);
next();
})
})
}
else {
next();
}

findByIdAndUpdate $set does not check for unique?

I have a schema and specify login is unique is true. When I use findByIdAndUpdate and pass query $set to update an user object, it did not throw back error when login is dup. Does anyone know why and how I should update an object and force schema validation?
Thanks!
// UserSchema.js
var schema = new Schema({
login: {
required: true,
unique: true
},
password: {
index: true,
type: String
}
});
// Update
UserSchema.findByIdAndUpdate('someID', { '$set': { login: 'abc' } }, function (error, user) {
callback(error, user);
});
You simply need to set runValidators to true:
findByIdAndUpdate(req.params.id, {$set: data}, {runValidators: true}, function (err, doc) {
if (err) {
// Handle validation errors
}
})
More here: http://mongoosejs.com/docs/api.html#findOneAndUpdate
Using the shorthand helper methods in Mongoose bypasses validation, so you need to use a 3 step approach:
Find
Edit
Save
For example:
// 1: Find
UserSchema.findById( 'someID',
function (err, user) {
if(!err){
// 2: Edit
user.login = 'abc';
// 3: Save
user.save(function (err, user) {
if (err) {
console.log(err);
return;
}
console.log('User saved: ' + user);
});
}
}
);

Categories

Resources