Mongoose ODM, change variables before saving - node.js

I want to create a model layer with Mongoose for my user documents, which does:
validation (unique, length)
canonicalisation (username and email are converted to lowercase to check uniqueness)
salt generation
password hashing
(logging)
All of these actions are required to be executed before persisting to the db. Fortunately mongoose supports validation, plugins and middleware.
The bad thing is that I cannot find any good material on the subject.
The official docs on mongoosejs.com are too short...
Does anyone have an example about pre actions with Mongoose (or a complete plugin which does all, if it exists)?
Regards

In your Schema.pre('save', callback) function, this is the document being saved, and modifications made to it before calling next() alter what's saved.

Another option is to use Getters. Here's an example from the website:
function toLower (v) {
return v.toLowerCase();
}
var UserSchema = new Schema({
email: { type: String, set: toLower }
});
https://mongoosejs.com/docs/tutorials/getters-setters.html

var db = require('mongoose');
var schema = new db.Schema({
foo: { type: String }
});
schema.pre('save', function(next) {
this.foo = 'bar';
next();
});
db.model('Thing', schema);

Related

Testing mongoose pre-save hook

I am quite new to testing nodejs. So my approach might be completely wrong. I try to test a mongoose models pre-save-hook without hitting the Database. Here is my model:
// models/user.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
UserSchema = new Schema({
email: {type: String, required: true},
password: {type: String, required: true}
});
UserSchema.pre('save', function (next) {
const user = this;
user.email = user.email.toLowerCase();
// for testing purposes
console.log("Pre save hook called");
next();
});
module.exports = mongoose.model("User", UserSchema);
As I said, I do not want to hit the Database with my test, so I tried using a sinon stub of the Users save() method:
// test/models/user.js
const sinon = require("sinon");
const chai = require("chai");
const assert = chai.assert;
const User = require("../../models/user");
describe("User", function(){
it("should convert email to lower case before saving", (done) => {
const user = new User({email: "Valid#Email.com", password: "password123"});
const saveStub = sinon.stub(user, 'save').callsFake(function(cb){ cb(null,this) })
user.save((err,res) => {
if (err) return done(err);
assert.equal(res.email,"valid#email.com");
done();
})
})
});
However, If I do it like that the pre-save hook will not be called. Am I on the wrong path or am I missing something? Or is there maybe another way of triggering the pre-save hook and testing its outcome? Thanks very much in advance!
Before we start: I'm looking for the same thing as you do and I've yet to find a way to test the different hooks in Mongoose without a database. It's important that we distinguish between testing our code and testing mongoose.
Validation is middleware. Mongoose registers validation as a pre('save') hook on every schema by default. http://mongoosejs.com/docs/validation.html
Considering that validate will always be added to the model and I wish to test the automated fields in my model, I've switched from save to validate.
UserSchema = new Schema({
email: {type: String, required: true},
password: {type: String, required: true}
});
UserSchema.pre('validate', function(next) {
const user = this;
user.email = user.email.toLowerCase();
// for testing purposes
console.log("Pre validate hook called");
next();
});
The test will now look like:
it("should convert email to lower case before saving", (done) => {
const user = new User({email: "VALID#EMAIL.COM", password: "password123"});
assert.equal(res.email,"valid#email.com");
}
So What About the Pre Save Hook?
Because I've moved the business logic for automatic fields from 'save' to 'validate', I'll use 'save' for database specific operations. Logging, adding objects to other documents, and so on. And testing this only makes sense with integration with a database.
I just faced the same issue and managed to solve it by extracting the logic out of the hook, making it possible to test it in isolation. With isolation I mean without testing anything Mongoose related.
You can do so by creating a function, that enforces your logic, with the following structure:
function preSaveFunc(next, obj) {
// your logic
next();
}
You can then call it in your hook:
mySchema.pre('save', function (next) { preSaveFunc(next, this); });
This will make the reference to this available inside the function, so you can work with it.
The extracted part can then be unit tested by overwriting the next function to a function without a body.
Hope this will help anyone as it actually was a pain to solve this with my limited knowledge on Mongoose.

New document from mongoose method

I want to extend model with new method 'create'. It will check requirements for document creation, create additional documents and so on.
Usually I call
var user = new User({});
But how can I create document from mongoose method itself? I.e.
User.methods.create = function(userObject,callback){
//some checks
var doc = ???;
doc.save(function(err){
if(err) return callback(err);
//saving done
callback(null,doc);
});
}
UPD:
Thx to #chridam's answer my final code now looks like this:
User.statics.create = function(userObject,callback){
//some checks
var doc = this.model('User')(userObject);
doc.save(function(err){
if(err) return callback(err);
//saving done
callback(null,doc);
});
}
Statics will allow for defining functions that exist directly on your Model, so instead of an instance model (like you have tried), define a static method on the User class. Example:
var userSchema = new Schema({ firstname: String, lastname: String });
// assign a function to the "statics" object of our userSchema
userSchema.statics.create = function (userObject) {
console.log('Creating user');
// use Function.prototype.call() to call the Model.create() function with the model you need
return mongoose.Model.create.call(this.model('User'), userObject);
};
I know this is not answering your question exactly, but it may be what you are looking for.
check out the mongoose middleware
http://mongoosejs.com/docs/middleware.html
There is a pre-save hook where you can do some validation and other things such as document creation.

mongoose best practices for objects

I have a user.js file that contains all my mongoose schema and methods, roughly like this:
var userSchema = mongoose.Schema({
name: { type: String, required: true, trim: true },
surname: { type: String, required: true },
});
var User = mongoose.model('User', userSchema);
Now, Is it best to expose functions that model the data? like this:
exports.addUser = function(data, next){
user = new User(data);
user.save(next);
};
exports.getAll = function(next){
var query = User.find();
query.exec(next);
};
or to expose just the User object to be used elsewhere like this?
module.exports = User; //or
exports.User = User;
I am also facing a problem derived from the second solution, suppose I want to add a list of cars to my userSchema, and the car schema is defined in another file car.js, then it means that I will have to expose the carSchema for my user.js file, right? This means I am actually nullifying the second solution I provided above, then, what is the right way of doing this sort of things?
Interesting question.
This is probably just a "syntactic sugar" thing but I am sticking with the second variant because there is IMHO no need to capsule the generation etc. mongoose is the wrapper around such stuff and one can then use the pure mongoose Models and Schemas. Does this sounds reasonable for you?
exports.User = User;

Mongoose + Express - What's the proper way to save the user in the document (audit info)

I'm writing an Express.js app that uses Mongoose ODM.
When a document is created/updated, I want it to automatically get some fields populated:
createdBy
createdOn
It seems to me that the right place to implement that would be in a Mongoose plugin that augments the document with those properties and provides defaults and/or mongoose middleware to populate the fields.
However, I have absolutely no idea how I could get the username from the session in my plugin.
Any suggestion?
/**
* A mongoose plugin to add mandatory 'createdBy' and 'createdOn' fields.
*/
module.exports = exports = function auditablePlugin (schema, options) {
schema.add({
createdBy: { type: String, required: true, 'default': user }
, createdOn: { type: Date, required: true, 'default': now }
});
};
var user = function(){
return 'user'; // HOW to get the user from the session ???
};
var now = function(){
return new Date;
};
You can't do this like this, bacause user function is added as a prototype method for this schema. In particular it is request/response independent. There is a hack however. If you have object obj of the type schema, then you can do
obj.session = req.session;
in request handler. Then you can access the session from the function. However this may lead to other issues ( for example running cron jobs on this collection ) and it looks like a very bad practice to me.
You can just do that manually, when a current user creates this schema object, can't you? Or create static method:
MySchema.statics.createObject = function ( user ) {
// create your object
var new_object = MySchema( ... );
// set necessary fields
new_object.createdBy = user.username;
}

How do you handle form validation, especially with nested models, in Node.js + Express + Mongoose + Jade

How are you handling form validation with Express and Mongoose? Are you using custom methods, some plugin, or the default errors array?
While I could possibly see using the default errors array for some very simple validation, that approach seems to blow up in the scenario of having nested models.
I personally use node-validator for checking if all the input fields from the user is correct before even presenting it to Mongoose.
Node-validator is also nice for creating a list of all errors that then can be presented to the user.
Mongoose has validation middleware. You can define validation functions for schema items individually. Nested items can be validated too. Furthermore you can define asyn validations. For more information check out the mongoose page.
var mongoose = require('mongoose'),
schema = mongoose.Schema,
accountSchema = new schema({
accountID: { type: Number, validate: [
function(v){
return (v !== null);
}, 'accountID must be entered!'
]}
}),
personSchema = new schema({
name: { type: String, validate: [
function(v){
return v.length < 20;
}, 'name must be max 20 characters!']
},
age: Number,
account: [accountSchema]
}),
connection = mongoose.createConnection('mongodb://127.0.0.1/test');
personModel = connection.model('person', personSchema),
accountModel = connection.model('account', accountSchema);
...
var person = new personModel({
name: req.body.person.name,
age: req.body.person.age,
account: new accountModel({ accountID: req.body.person.account })
});
person.save(function(err){
if(err) {
console.log(err);
req.flash('error', err);
res.render('view');
}
...
});
I personaly use express-form middleware to do validation; it also has filter capabilities. It's based on node-validator but has additional bonuses for express. It adds a property to the request object indicating if it's valid and returns an array of errors.
I would use this if you're using express.

Resources