How do I find missing schema fields on a Mongoose query - node.js

If I add new fields directly to my MongoDB database and I forget to add them to my Mongoose schema, how can I alert myself to the problem without it failing silently.
The following example shows that all fields are returned from a query (regardless of the schema) but undefined if you access the key.
var mongoose = require('mongoose');
var user_conn = mongoose.createConnection('mongodb://db/user');
var userSchema = mongoose.Schema({
email: String,
// location: String,
admin: Boolean
});
var User = user_conn.model('User', userSchema);
User.findOne({email: 'foo#bar.com.au'}, function (err, doc) {
if (err) return console.log(err);
console.log(doc);
console.log(doc.email);
console.log(doc.location);
});
Result:
{ _id: 57ce17800c6b25d4139d1f95,
email: 'foo#bar.com.au',
location: 'Australia',
admin: true,
__v: 0 } // <-- console.log(doc);
foo#bar.com.au // <-- console.log(doc.email);
undefined // <-- console.log(doc.location);
I could read each doc key and throw an error if undefined, but is this the only way?
Versions
Node.js: 6.5.0
Mongoose: 4.6.0

You can set strict to false on the schema so it will save all properties even if they are not in schema:
var userSchema = mongoose.Schema({
email: String,
admin: Boolean
}, {strict: false});
http://mongoosejs.com/docs/guide.html#strict
In order to get a property which is not in schema you need to use doc.toObject() and use the returned object, or to use doc.get('location')

Following on from Amiram's answer. I now use lean() to get the object during a query but when I need to update or save it still looks up the schema.
User.findOne({email: 'foo#bar.com.au'}).lean().exec(function (err, doc) {
console.log(doc);
console.log(doc.email);
console.log(doc.location);
});

Related

Mongoose: OverwriteModelError: Cannot overwrite `users` model once compiled

I'm using Mongoose to manage a MongoDB server, and all other solutions with this error have not helped. The models aren't defined anywhere else, and the only issue I can think of is that the tables/models already exist on the MongoDB server, but other than that I've never had this issue before.
My current code is this:
const mongoose = require('mongoose');
mongoose.connect(process.env.DATABASE_URL, {useNewUrlParser: true, useUnifiedTopology:true});
const users = require('../../models/Users.schema')
const accounts = require('../../models/Account.schema')
export default (req, res) => {
users.findOne({ email: req.body.email }, function (err, user) {
console.log(user)
accounts.findOne({ userId: user._id }, function (err, account) {
console.log(account)
return res.json({
error: false,
body: account
})
})
})
}
Users schema (mainly for an example, this issue happens with all schemas)
const mongoose = require('mongoose');
const usersSchema = new mongoose.Schema({
name: String,
email: String,
image: String,
createdAt: Date,
updatedAt: Date
})
module.exports.users = mongoose.model('users', usersSchema);
What I'm trying to do is get the data from NextAuth that isn't provided normally, such as the accessToken (session.accessToken is not the right accessToken).
I'm not sure what to do, and I'll take any help I can get.
Thanks!
The error is occurring because you already have a schema defined, and then you are defining the schema again

How to associate the ObjectId of the parent schema to the child schema in the same transaction?

I have two types of entities, users and families, with a many-to-one relationship between families and users, so one family can have many users but one user will only have one family. I attempted to create a Mongo schema that tries to achieve this relationship, but not sure if this is the right way to do it.
I have a button on my HTML page that when clicked, will generate a family code and also create a new family attribute for the family entity. However, I'm unable to connect that newly generated family.ObjectId to the user's familyid attribute in the user entity.
Any idea what I'm doing wrong?
My Models:
Family Model:
var mongoose = require("mongoose");
var shortid = require('shortid');
//Set Schema
var familySchema = mongoose.Schema({
individuals: [{
type: mongoose.Schema.Types.ObjectId,
ref: "User"
}],
familyCode: {
type: String,
'default': shortid.generate
}
});
//setup and export the model
module.exports = mongoose.model("Family", familySchema);
Users Model:
var mongoose = require("mongoose");
var passportLocalMongoose = require("passport-local-mongoose");
var UserSchema = new mongoose.Schema({
username: String,
password: String,
image: String,
firstName: String,
lastName: String,
accountid: String,
isAdmin: {type: Boolean, default: false},
userlabel: String,
familyid:
{ type : mongoose.Schema.Types.ObjectId,
ref: "Family"}
});
UserSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("User", UserSchema);
My Route:
router.post("/familySetup", function(req, res){
if(!req.body.familyid){
var familyCode = shortid.generate();
console.log(familyCode);
// var individuals = {id:req.user._id}
var newFamily = {familyCode:familyCode};
Family.create(newFamily, function(err, newFamily){
if(err){
req.flash("error", "something seems to have gone amiss. Please try again.")
console.log(err);
res.redirect("home")
}else{
var updatedUser = {familyid:newFamily._id}
console.log(updatedUser);
User.findByIdAndUpdate(req.params.user_id, updatedUser, function(req, res){
if(err){
console.log(err);
}else{
//redirect to index
res.redirect("/familySetup");
}
});
}
});
}else{
alert("You already have a family account");
res.redirect("back");
}
});
Based on the error I get, I am able to create a familyCode and am able to create the variable, updatedUser. But it does not update the user with the new attribute. This is the error when I run the code:
ByJyRqb_Z
{ familyid: 59941dd6589f9a1c14a36550 }
events.js:141
throw er; // Unhandled 'error' event
^
TypeError: Cannot read property 'redirect' of null
at /home/ubuntu/workspace/mbswalay/routes/family.js:44:28
at Query.<anonymous> (/home/ubuntu/workspace/mbswalay/node_modules/mongoose/lib/model.js:3755:16)
at /home/ubuntu/workspace/mbswalay/node_modules/mongoose/node_modules/kareem/index.js:277:21
at /home/ubuntu/workspace/mbswalay/node_modules/mongoose/node_modules/kareem/index.js:131:16
at nextTickCallbackWith0Args (node.js:436:9)
at process._tickCallback (node.js:365:13)
It doesn't seem to be an error of mongodb, it's related to the route configuration, which seems make the response object to be null
User.updateOne(
{ _id: req.params.user_id },
{ $set: { familyid: newFamily._id } }
)
You have to set the value of an attribute using $set operator.

Mongoose - inserting subdocuments

I have a user model, and a log model. The log model is a subdocument of user model. So in my user model I have:
var mongoose = require('mongoose');
var Log = require('../models/log');
var UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true
},
logsHeld: [
Log
]
});
Then in my 'Log' model I have:
var mongoose = require('mongoose');
var logSchema = new mongoose.Schema({
logComment: {
type: String,
},
});
module.exports = mongoose.model('Log', logSchema);
So upon creation of a 'user', the 'logsHeld' always begins empty. I want to know how to add subdocuments to this user model.
I've tried doing this POST method:
router.post('/createNewLog', function(req, res) {
var user = new User ({
logssHeld: [{
logComment: req.body.logComment
}]
});
user.save(function(err) {
if(err) {
req.flash('error', 'Log was not added due to error');
return res.redirect('/home');
} else {
req.flash('success', 'Log was successfully added!');
return res.redirect('/home');
}
});
});
But this doesn't work. It also includes a 'new User' line, which I don't think I need given this would be for an existing user.
You need to use the logSchema instead of the Log model as your subdocument schema in User model. You can access the schema as follows:
var mongoose = require('mongoose');
/* access the Log schema via its Model.schema property */
var LogSchema = require('../models/log').schema; // <-- access the schema with this
var UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true
},
logsHeld: [LogSchema]
});
Picking up from your comments in another answer where you are facing another issue
WriteError({"code":11000,"index":0,"errmsg":"E11000 duplicate key
error index: testDB.users.$email_1 dup key:
you are getting this because there's already a document in your users collection that has most probably a null value on the email field. Even though your schema does not explicitly specify an email field, you may have an existing old and unused unique index on users.email.
You can confirm this with
testDB.users.getIndexes()
If that is the case and manually remove the unwanted index with
testDB.users.dropIndex(<index_name_as_specified_above>)
and carry on with the POST to see if that has rectified the error, I bet my $0.02 that there is an old unused unique index in your users collection which is the main issue.
Try using logSchema which references only the subdocument schema, Log refers to the entire contents of ../models/log
var UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true
},
logsHeld: [
logSchema
]
});
Documentation: http://mongoosejs.com/docs/subdocs.html
Try push to insert item in array in mongoose
var user = new User;
user.logssHeld.push({
logComment: req.body.logComment
});
user.save(function(err, doc) {
//DO whatever you want
});
see the docs here

Mongoose returning empty array of ObectID, which isn't

I am using the following Mongoose Schema :
var userSchema = new mongoose.Schema({
...
sentFriendsRequests: [{
type : ObjectId,
}]
)};
I am adding some ObjectIds to the sentFriendsRequests
User.update({ _id: userId },
{ $push: { sentFriendsRequests: targetId }},
{safe: true, upsert: true}, function(err, result) {
if (err || !result) {
done(err);
}
done(null);
});
This seems to be working properly, because as I am using Mongolab to host my Database, when displaying documents on screen I can see that the ObjectIds are added to the array with success :
"receivedFriendsRequests": [
"5720c659571a718705d58fc3"
]
The weird thing is that when querying this array, Mongoose always return an empty one...
User.find({ _id: userId}, function(err, res) {
console.log(res[0].sentFriendsRequests);
});
// prints []
Have confusion of mongodb with mongoose.
Mongoose need define Schema but mongodb is nope.
To define new ObjectId in mongodb:
var ObjectId = require('mongodb').ObjectID
var objectId = new ObjectID();
in Mongoose:
var mongoose = require('mongoose');
var objectId = new mongoose.Types.ObjectId;
I finally found that using var ObjectId = require('mongodb').ObjectID; makes Mongoose to return empty array, whereas using mongoose.Schema.Types.ObjectId works properly. I don't know how to explain this.

Validation on user inputs with MongoDB and mongoose?

I would an unified method to validate my schemas assuming a user input, so not only apply the built-in validation on save/update, but also on find(), etc..
var User = mongoose.model("User", new Schema({
name: {type: String, minlength: 5, maxlength: 128, required: true, unique: true});
}));
What I want is to run validators every time before I run the queries with mongoose, to assure that the user inputs comply with the global schema rules.
Something like that in my route:
var username = $.get["username"], //An input from GET querystring
User = mongoose.model("User");
User.validate({name: username}, function(err) {
if (err) return console.log("not valid input"); //i.e. too short
//run query if valid
});
Is there a plugin (assumed that I'm not using Express) or maybe other already included in mongoose for that?
Documentation: http://mongoosejs.com/docs/validation.html
It is supported in mongoose by default. If you are looking for generic validation before each save operation you can specify the field to be validated path and the validation validate(function(valueEntered, howToRespond). If the validation is not passed the error will be thrown as shown in the example below.
Example: Using bluebird for sake of convenience. The following snippet validates the email, before every save operation.
var mongoose = require('bluebird').promisifyAll(require('mongoose'));
var Schema = mongoose.Schema;
var UserSchema = new Schema({
name: String,
email: {
type: String,
lowercase: true
},
password: String,
});
UserSchema
.path('email')
.validate(function(value, respond) {
var self = this;
return this.constructor.findOneAsync({ email: value })
.then(function(user) {
if (user) {
if (self.id === user.id) {
return respond(true);
}
return respond(false);
}
return respond(true);
})
.catch(function(err) {
throw err;
});
}, 'The specified email address is already in use.');

Resources