How to cancel a mongoose query in a 'pre' hook - node.js

I am implementing some kind of caching for my 'find' queries on a certain schemas, and my cache works with the pre\post query hooks.
The question is how can I cancel the 'find' query correctly?
mySchema.pre('find', function(next){
var result = cache.Get();
if(result){
//cancel query if we have a result from cache
abort();
} else {
next();
}
});
so that this promise will be fulfilled?
Model.find({..})
.select('...')
.then(function (result) {
//We can reach here and work with the cached results
});

I was unable to find a reasonable solution to this myself for another non-caching reason but if your own specific caching method isn't too important I'd recommend you look at mongoose-cache, works well and has simple settings thanks to it's dependency: node-lru-cache, check that out for more options.

you may want to check out mongoose validators, that seems like a better way to handle controlling whether or not an object gets created.
You can create a custom validate function that will throw an error in the Model.save function, causing it to fail. Here is a code snippet from the docs:
// make sure every value is equal to "something"
function validator (val) {
return val == 'something';
}
new Schema({ name: { type: String, validate: validator }});
// with a custom error message
var custom = [validator, 'Uh oh, {PATH} does not equal "something".']
new Schema({ name: { type: String, validate: custom }});
// adding many validators at a time
var many = [
{ validator: validator, msg: 'uh oh' }
, { validator: anotherValidator, msg: 'failed' }
]
new Schema({ name: { type: String, validate: many }});
// or utilizing SchemaType methods directly:
var schema = new Schema({ name: 'string' });
schema.path('name').validate(validator, 'validation of {PATH} failed with value {VALUE}');
Found that here if you want to look into it more. Hope that helps someone!
http://mongoosejs.com/docs/api.html#schematype_SchemaType-validate

Related

Mongoose schema - force one property to be an existing value (enum) of different property on same schema

I have this schema:
const schema = new Schema({
deviceName: { type: String, required: true, unique: true},
category: { type: String, enum: DEVICE_CATEGORIES, required: true },
incompatibleWithDevices: [String]
});
const DeviceModel = model(
"Device",
schema
);
incompatibleDevices should be a list of other devices (by device name) that the device in question cannot work with. So this array should only accept device names that actually exist in the db. For business logic purposes, I can't have a one-to-many relationship here.
I thought about somehow making an enum of all device names that currently exist, but since this is constantly changing as devices get added/removed, I can't think of a way to do this.
Does anyone have an idea of how to achieve this? Perhaps something with a custom validator?
Thanks in advance, any help is greatly appreciated!
It seams that before creating new Device document, you can add validation directly in the backend logic, checking if devices that you are sending exists in the database or not.
You can use $in operator for that:
const createDevice = async (req, res) => {
try {
const { deviceName, category, incompatibleWithDevices} = req.body;
const incompatibleDevices = await Devices.find({ deviceName: { $in incompatibleWithDevices }});
// Here, you do the validation if some deviceName are maybe invalid.
// At least one device will be invalid if returned array is not the same
// size as the input array.
if (incompatibleDevices.length !== incompatibleWithDevices.length) {
return res.status(400).json({ sucess: false });
}
// Here you can add the logic that creates the new device since
// all inputs are valid.
} catch (error) {
return res.status(400).json({ success: false })
}
}

Get isModified in validator

Is there a way to check if a path has been modified in a validator? Do I need to check or do validators only run if the path was changed?
EDIT:
More specifically, I am trying to make sure an author exists before I insert an id:
var BookSchema = new Schema({
title: { type: String, required: true },
authorId: { type: Schema.Types.ObjectId, ref: 'Author' }
});
BookSchema.path('authorId').validate(function(authorId, done) {
Author.getAuthorById(authorId, function(err, author) {
if (err || !author) {
done(false);
} else {
done(true);
}
});
}, 'Invalid author, does not exist');
In this case I only want this to validate if authorId is set or if it changes. Do I need to check if changed in the function or can I assume that this validator only gets called if the authorId changes and is not null/undefined?
This makes it look like I might be able to call isModified, however I don't see that as a function on 'this'.
Mongoose validation only when changed
Yes, validators are only run if the path is changed, and they also only run if they're not undefined. Except the Required validator, which runs in both cases.

Why Mongoose doesn't validate empty document?

Let's say I have a very simple schema with a custom validation function that always returns false.
var MofoSchema = new mongoose.Schema({
name: String
});
MofoSchema.path('name').validate(function (value) {
console.log("I'm validating");
return false;
}, 'No pasaran');
mongoose.model('Mofo', MofoSchema);
Then I create a new instance of my document and I validate it:
var Mofo = mongoose.model('Mofo');
var mofo = new Mofo({name: "Tarte flambée"});
mofo.validate(function(err) {
console.log(err);
});
Perfect, the custom validator function is called and err is filled.
But now I do the same with no data:
var Mofo = mongoose.model('Mofo');
var mofo = new Mofo({});
mofo.validate(function(err) {
console.log(err);
});
The custom validator function is not called and err is undefined.
Why? I don't understand why Mongoose is not running the custom validator.
Is this behaviour by design? Is it a bug?
Should I hack a turnaround? Should I check manually for empty data before validation?
Am I doing something wrong?
PS: If you call save, the document will be saved as empty in MongoDB despite of the custom validator.
I think mongoose will only validate for existing field.
So you can use 'null' value to activate validation
var mofo = new Mofo({name: null});
For empty or undefined
var MofoSchema = new mongoose.Schema({
name: {type: String, required: true}
});
MofoSchema.path('name').validate(function (value) {...}, 'no pasaran');
You can do:
var mofo = new Mofo({ name: { type: String, default: ''} });
This will ensure you always have a value on that key to trigger validation. It also makes your schema definition easier to read.

mongoose doc.save fails without error in schema method

Using mongoose i am doing:
var postSchecma = mongoose.Schema({
title: String,
body: String,
link: String,
voting: {
has: {
type: Boolean,
default:
false
},
canVoteFor: [mongoose.Schema.Types.Mixed],
votedFor:{},
voteDates:{}
},
comments: [mongoose.Schema.Types.Mixed],
date: {
type: mongoose.Schema.Types.Mixed,
default:
new Date().getTime()
}
}, {
strict: false,
safe:true
})
and
postSchecma.methods.vote = function(voteFor, callback) {
var self = this;
if(self.voting.canVoteFor.indexOf(voteFor) < 0) {
callback(new Error('Error: Invalid Thing To Vote For'));
return;
}
this.voting.voteDates[voteFor].push(new Date().getTime())
this.voting.votedFor[voteFor]++
s = this;
this.save(function(err) {
if(err) {
callback(err)
}
console.log(err);
console.log("this:"+ s);
callback(s)
})
}
in postSchecma.methods.vote the value of this.voting.votedFor[voteFor] is correct. but when I query the db it is the old value. if it helps i am using the db in 2 files and the methods may not be exact duplicates.
I also know it is something with mongoose because I can change the record to a different value with a mongoDB GUI and it works fine.
let me know if you need any more info,
thanks,
Porad
Any field in your schema that's defined as {} or Mixed must be explicitly marked as modified or Mongoose won't know that it has changed and that Mongoose needs to save it.
In this case you'd need to add the following prior to the save:
this.markModified('voting.voteDates');
this.markModified('voting.votedFor');
See docs on Mixed here.
Turns out that this also sometimes applies for non-Mixed items, as I painfully discovered. If you reassign an entire sub-object, you need to use markModified there as well. At least... sometimes. I didn't use to get this error, and then I did, without changing any relevant code. My guess is that it was a mongoose version upgrade.
Example! Say you have...
personSchema = mongoose.Schema({
name: {
first: String,
last: String
}
});
...and then you call...
Person.findById('whatever', function (err, person) {
person.name = {first: 'Malcolm', last: 'Ocean'};
person.save(function (err2) {
// person.name will be as it was set, but this won't persist
// to the database
});
});
...you will have a bad time unless you call person.markModified('name') before save
(or alternatively, call both person.markModified('name.first') and person.markModified('name.last') ...but that seems clearly inferior here)

Mongoose - how to set document property to be another document

I am trying to fake non-array nested documents by creating a separate model for the embedded document, validating it and if the validation is successful, setting it as the main document's property.
In a POST /api/document route I am doing teh following:
var document = new DocumentModel({
title: req.body.title
});
var author = new AuthorModel({
name: req.body.author.name
});
author.validate( function( err ) {
if (!err) {
document.author = author.toObject();
} else {
return res.send( err, 400 );
}
});
console.log( document );
But it doesn't seem to work - console prints out the document without author. I am probably missing something very obvious here, maybe I need to do some kind of nested callbacks, or maybe I need to use a special setter method, like document.set( 'author', author.toObject() )... but I just can't figure it our on my own right now.
Looks like author.validate is async so that your console.log(document); statement at the bottom executes before the callback where you set document.author. You need to put the processing that depends on document.authorbeing set inside of the callback.
It looks like the answer is to use a callback to set the document.author and to define author in the Schema.
Like #JohnnyHK pointed out, I can't log the document to console using my original code because the author.validate is async. So, the solution is to either wrap the console.log (and probably further document.save() inside the callback of author.validate()
It also seems that Mongoose does not "set" any properties for a model that are not defined in the Schema. Since my author is an object, I had to set the author field in the Schema to Mixed, like this.
The following code works:
var DocumentModel = new Schema({
title: { type: String, required: true },
author: {}
});
var AuthorModel = new Schema({
name: { type: String, required: true }
});
app.post("/api/documents", function(req, res) {
var document = new DocumentModel({
title: req.body.title
});
var author = new AuthorModek({
title: req.body.author.name
});
author.validate( function( err ) {
if (!err) {
document.author = author;
docment.save( function( err ) {
...
});
} else {
return res.send( err, 400 );
}
})
});

Resources