mongoose doc.save fails without error in schema method - node.js

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)

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) { ... });

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.

How to cancel a mongoose query in a 'pre' hook

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

Create unique autoincrement field with mongoose [duplicate]

This question already has answers here:
Mongoose auto increment
(15 answers)
Closed 2 years ago.
Given a Schema:
var EventSchema = new Schema({
id: {
// ...
},
name: {
type: String
},
});
I want to make id unique and autoincrement. I try to realize mongodb implementation but have problems of understanding how to do it right in mongoose.
My question is: what is the right way to implement autoincrement field in mongoose without using any plugins and so on?
const ModelIncrementSchema = new Schema({
model: { type: String, required: true, index: { unique: true } },
idx: { type: Number, default: 0 }
});
ModelIncrementSchema.statics.getNextId = async function(modelName, callback) {
let incr = await this.findOne({ model: modelName });
if (!incr) incr = await new this({ model: modelName }).save();
incr.idx++;
incr.save();
return incr.idx;
};
const PageSchema = new Schema({
id: { type: Number , default: 0},
title: { type: String },
description: { type: String }
});
PageSchema.pre('save', async function(next) {
if (this.isNew) {
const id = await ModelIncrement.getNextId('Page');
this.id = id; // Incremented
next();
} else {
next();
}
});
Yes, here's the "skinny" on that feature.
You need to have that collection in your mongo database. It acts as concurrent key allocation single record of truth if you want. Mongo's example shows you how to perform an "atomic" operation to get the next key and ensure that even there are concurrent requests you will be guaranteed to have the unique key returned without collisions.
But, mongodb doesn't implement that mechanism natively, they show you how to do it. They only provide for the _id to be used as unique document key. I hope this clarifies your approach.
To expand on the idea, go ahead and add that mongo suggested implementation to your defined Mongoose model and as you already guessed, use it in Pre-save or better yet pre-init event to ensure you always generate an id if you work with a collection server side before you save it to mongo.
You can use this.
This package every time generate unique value for this.
Package Name : uniqid
Link : https://www.npmjs.com/package/uniqid
Ignore all the above. Here is the solution
YourModelname.find().count(function(err, count){
req["body"].yourID= count + 1;
YourModelname.create(req.body, function (err, post) {
if (err) return next(err);
res.json(req.body);
});
});

Incorrect Subdocument Being Updated?

I've got a Schema with an array of subdocuments, I need to update just one of them. I do a findOne with the ID of the subdocument then cut down the response to just that subdocument at position 0 in the returned array.
No matter what I do, I can only get the first subdocument in the parent document to update, even when it should be the 2nd, 3rd, etc. Only the first gets updated no matter what. As far as I can tell it should be working, but I'm not a MongoDB or Mongoose expert, so I'm obviously wrong somewhere.
var template = req.params.template;
var page = req.params.page;
console.log('Template ID: ' + template);
db.Template.findOne({'pages._id': page}, {'pages.$': 1}, function (err, tmpl) {
console.log('Matched Template ID: ' + tmpl._id);
var pagePath = tmpl.pages[0].body;
if(req.body.file) {
tmpl.pages[0].background = req.body.filename;
tmpl.save(function (err, updTmpl) {
console.log(updTmpl);
if (err) console.log(err);
});
// db.Template.findOne(tmpl._id, function (err, tpl) {
// console.log('Additional Matched ID: ' + tmpl._id);
// console.log(tpl);
// tpl.pages[tmpl.pages[0].number].background = req.body.filename;
// tpl.save(function (err, updTmpl){
// if (err) console.log(err);
// });
// });
}
In the console, all of the ID's match up properly, and even when I return the updTmpl, it's saying that it's updated the proper record, even though its actually updated the first subdocument and not the one it's saying it has.
The schema just in case:
var envelopeSchema = new Schema({
background: String,
body: String
});
var pageSchema = new Schema({
background: String,
number: Number,
body: String
});
var templateSchema = new Schema({
name: { type: String, required: true, unique: true },
envelope: [envelopeSchema],
pagecount: Number,
pages: [pageSchema]
});
templateSchema.plugin(timestamps);
module.exports = mongoose.model("Template", templateSchema);
First, if you need req.body.file to be set in order for the update to execute I would recommend checking that before you run the query.
Also, is that a typo and req.body.file is supposed to be req.body.filename? I will assume it is for the example.
Additionally, and I have not done serious testing on this, but I believe your call will be more efficient if you specify your Template._id:
var template_id = req.params.template,
page_id = req.params.page;
if(req.body.filename){
db.Template.update({_id: template_id, 'pages._id': page_id},
{ $set: {'pages.$.background': req.body.filename} },
function(err, res){
if(err){
// err
} else {
// success
}
});
} else {
// return error / missing data
}
Mongoose doesn't understand documents returned with the positional projection operator. It always updates an array of subdocuments positionally, not by id. You may be interested in looking at the actual queries that mongoose is building - use mongoose.set('debug', true).
You'll have to either get the entire array, or build your own MongoDB query and go around mongoose. I would suggest the former; if pulling the entire array is going to cause performance issues, you're probably better off making each of the subdocuments a top-level document - documents that grow without bounds become problematic (at the very least because Mongo has a hard document size limit).
I'm not familiar with mongoose but the Mongo update query might be:
db.Template.update( { "pages._id": page }, { $set: { "pages.$.body" : body } } )

Resources