Node, Mongoose: on save() "VersionError: No matching document found." - node.js

I'm new to mongoose so this is probably something very simple .. however.
I have a very simple schema that contains a simple array of numbers:
userSchema = new mongoose.Schema({
name : String,
tag_id : String,
badges : [Number]
});
var user = mongoose.model( 'User', userSchema );
Later I want to add a badge to the user. So..
user.findOne({tag_id:tagid}, function(err,doc) {
if (!doc) callback(false, 'no doc');
// check to see if badge is in array, if not add it
if ( doc.badges.indexOf(badgeNum) == -1 ) {
doc.badges.push(badgeNum);
doc.save( function(err) {
if (err) callback( false, err );
else callback( true, '');
});
} else {
callback( false, 'user already had badge' )
}
});
However whenever I run that code I get a 'VersionError: No matching document found.'
I did a little googling and found reference to versioning being added in 3.x mongoose and that it's generally all handled internally. It should be noted that I loaded this user data straight into mongo via json and the mongo commandline ( so it wouldn't have the versioning variable defined by default, but I suspect that mongoose would create it if it didn't find it... bad assumption?)
EDIT: Sorry my simplified example had an error in it.

I solved this problem by adding versioning fields to my imported dataset and modifying the schema to associate it with versionKey.
http://mongoosejs.com/docs/guide.html#versionKey
I do wonder, however if there isn't a setting that lets mongoose add the default versionkey (__v) to data that lacks it.

You are trying to assign the testSchema to the user object instead of the userSchema so it is looking for an document type that hasn't been created.
Update the user assignment to the following:
var user = mongoose.model('User', userSchema);
I would suggest you follow the guides if you aren't sure what you're doing.

Related

MongoDB: findOne returns null but sub document exists in collection

I am adding custom list in db. After Calling a random custom list, the custom list created with sub document list in db. But it's showing it's sub document in console is null. My code ->
My Sub document Scheme->
const toSchema=new mongoose.Schema({
name:{ type:String,required:true}
});
My main custom list Schema->
const newSchema=new mongoose.Schema({
name:String,
sch:[toSchema]
});
const Work=mongoose.model("work",newSchema);
Now, I adding the collections & show it to console->
app.get("/post/:get",function(req,res){
var ch=req.params.get;
const item=new Todo({
name:ch
});
const work=new Work({
name:ch,
sch:item
});
work.save();
Work.findOne({name:ch},function(err,works){
if(err){
console.log(err);
}
else{
console.log(works);
}
});
});
I am typing a custom list "home" & it's created successfully in
db. But the sub document showing null.
Please, help.
It shows null because .save() is asynchronous in nature, so you need to wait for it's execution to complete before trying to find, but if you just want to find the document you are saving, then you don't need to use find as .save() returns the document you are saving
work.save(function(err, document) {
if (err)
console.error(err);
else
console.log(document);
});
But if you are planning to use .find() you need to wait for .save() execution to finish
It is showing null because in your Schema you declared sch as a array and trying to store object in it.
const newSchema=new mongoose.Schema({
name:String,
sch:[toSchema] // Declared Array
});
const Work=mongoose.model("work",newSchema);
That is why it is null.
If you want store object in it (In your case const item) then you need to declare sch as a object. Change your newSchema as mentioned below
const newSchema = new mongoose.Schema({
name:String,
sch:{toSchema}
});
const Work=mongoose.model("work",newSchema);
Then it works.

MongoDB: handling auto-incrementing model id's instead of Mongo's native ObjectID

Due to a management decision, we are using userId for the users collection, postId for the posts collection, and topicId for the topics collection, instead of '_id' for each collection as the unique identifier.
This causes a few problems getting started - one of the problems I have encountered is with upserts -
Using Mongoose, we have a schema that restricts userId to be a unique value - but when doing an update on a user model, with upsert set to true, MongoDB appears to only look at the ObjectIds of a collection to see if the same one exists - it doesn't check to see if a model already exists with the same userId - therefore Mongo does an insert instead of an update.
let me illustrate this with some data:
let's say the user's collection has one document:
{
_id:'561b0fad638e99481ab6d84a'
userId:3,
name:'foo'
}
we then run:
User.update({userId:3},{"$set":{name:'bar'},{upsert:true},function(err,resp){
if(err){
// "errMessage": "insertDocument :: caused by :: 11000 E11000 duplicate key error index: app42153482.users.$userId_1 dup key: { : 3 }",
}
});
one would think that MongoDB would find the existing document with userId:3 and udpate it, so there must be something I am doing wrong since it's giving me the duplicate key error?
Typically the default value ObjectId is more ideal for the _id. Here, in this situation you can either override the default _id or you can have your own field for id(like userId in your case).
Use a separate counters collection to track the last number sequence used. The _id field contains the sequence name and the seq field contains the last value of the sequence.
Insert into the counters collection, the initial value for the userid:
db.counters.insert( {
_id: "userid",
seq: 0 } )
Create a getNextSequence function that accepts a name of the sequence. The function uses the findAndModify() method to atomically increment the seq value and return this new value:
function getNextSequence(name) {
var ret = db.counters.findAndModify(
{
query: { _id: name },
update: { $inc: { seq: 1 } },
new: true
}
);
return ret.seq;
}
Use this getNextSequence() function during insert().
db.users.insert(
{
_id: getNextSequence("userid"),
name: "Sarah C."
}
)
db.users.insert(
{
_id: getNextSequence("userid"),
name: "Bob D."
}
)
This way you can maintain as many sequences as you want in the same counter collection. For the upsert issue, check out the Optimistic Loop block in this link Create an auto-increment sequence field.
The second approach is to use a mongoose middleware like mongodb-autoincrement.
Hope it helps.
I don't know which versions of MongoDB and Mongoose you are using, but I couldn't reproduce your problem with MongoDB 3.0 and Mongoose 4.1.10.
I made a sample for you which will create and save a new user, update (using upsert) it, and create another one through an upsert. Try running this code:
"use strict";
var mongoose=require("mongoose");
var Schema = require('mongoose').Schema;
var ObjectId = mongoose.Schema.Types.ObjectId;
// Connect to test
mongoose.connect("mongodb://localhost:27017/test");
// Lets create your schema
var userSchema = new Schema({
_id: ObjectId,
userId: {type: Number, unique: true },
name: String
});
var User = mongoose.model("User", userSchema, "Users");
User.remove() // Let's prune our collection to start clean
.then( function() {
// Create our sample record
var myUser = new User({
_id:'561b0fad638e99481ab6d84a',
userId:3,
name:'foo'
});
return myUser.save();
})
.then( function() {
// Now its time to update (upsert userId 3)
return User.update({userId:3},{"$set":{name:'bar'}},{upsert:true});
})
.then( function() {
// Now its time to insert (upsert userId 4)
return User.update({userId:4},{"$set":{name:'bee'}},{upsert:true});
})
.then( function() {
// Lets show what we have inserted
return User.find().then(function(data) {console.log(data)});
})
.catch( function(err) {
// Show errors if anything goes wrong
console.error("ERROR", err);
})
.then( function() {
mongoose.disconnect();
});
Following the documentation (of MongoDB 3.0) upsert:true will only not insert a non-existing document if your query conditions match on the _id field.
See: https://docs.mongodb.org/manual/reference/method/db.collection.update/#mongodb30-upsert-id
Why are you not using the user_name for a user as unique id?
Because auto-incrementing fields as ids are a bad practice to use in a mongodb environment, especially if you want to use sharding
=> all your inserts will occur on the latest shard
=> the mongodb cluster will have to rebalance often / redistribute the data around.
(Currently this will not occur on your system as you still use the generated _id field)
You can off course also create a unique index on the user_id field:
https://docs.mongodb.org/manual/core/index-unique/#index-type-unique

Mongoose Saving _id's as Numbers

I would like to save an _id as a Number as seen in this documentation:
_id : Number,
Taken from here: http://mongoosejs.com/docs/populate.html
However, when using this code, I recieve no errors when saving data to the Model. If I remove this line it saves without failure as an ObjectID
My code:
var UserSchema = new Schema({
_id: Number
});
mongoose.model('User', UserSchema)
Mongoose automatically assigns an _id to every document you create being consistent with general MongoDB documents with an ObjectId by default. To change this behavior, just turn it off:
var UserSchema = new Schema({
_id: Number
},{ "_id": false });
mongoose.model('User', UserSchema)
So the _id generation is disabled by this option, and this could cause an error in a "top level" schema unless of course you define the field yourself. When you do then the type given is respected and used.
Without the option, the default is used and overrides any declaration you used in the schema. So this code now works as expected:
user = new User({ "_id": 1 });
user.save(function(err,doc) {
if (err) throw err; // but wont fail to cast type now
console.log( doc );
});

How to populate the User object with Mongoose and Node

I am trying to add a couple of attributes to the scaffolded MEAN.js User entity.
locationName: {
type: String,
trim: true
}
I also have created another entity Book connected with User. Unfortunately, I think I do not quite grasp the concept behind the populate method because I am not able to "populate" the User entity with the locationName attribute.
I tried the following:
/**
* List of Books
*/
exports.list = function(req, res) {
Book.find().sort('-created').populate('user', 'displayName', 'locationName').exec(function(err, books) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(books);
}
});
};
Unfortunately, I get the following error:
/home/maurizio/Workspace/sbr-v1/node_modules/mongoose/lib/connection.js:625
throw new MongooseError.MissingSchemaError(name);
^
MissingSchemaError: Schema hasn't been registered for model "locationName".
Any suggestion?
Thanks
Cheers
The error is clear, you should have a schema for the locationName.
If your location is just a string property in your user model and does not refer to separate model, you don't need and shouldn't use populate with it, it will simply be returned as a property of the returned user object from mongoose find() method.
If your want to make your location a stand alone entity (different mongodb document), you should have a mongoose model that defines your location object, aka have a file in your app\models name for example: location.server.model.js that contains something like:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var LocationSchema = new Schema({
_id: String,
name: String
//, add any additional properties
});
mongoose.model('Location', LocationSchema);
Note that the _id here replaces the auto generated objectId, so this has to be unique, and this the property you should refer to in your User object, meaning if you have a location like this:
var mongoose = require('mongoose'),
Location = mongoose.model('Location');
var _location = new Location({_id:'de', name:'Deutschland'});
you should refer to it in your User object like this:
var _user=new User({location:'de'});
//or:
var _user=new User();
_user.location='de';
then you should be able to populate your location object with your user, like this:
User.find().populate('location').exec(function(err, _user) {
if (err) {
//handle error
} else {
//found user
console.log(_user);
//user is populated with location object, makes you able to do:
console.log(_user.location.name);
}
});
I suggest you to further read in mongodb data modeling and mongoose Schemas, Models, Population.

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