I am creating a Mongodb database using Mongoose with table structure similar to the one in the Group.js file listed below. When my code hits the findOne call, I am receiving the cast error listed below:
Cast to ObjectId failed for value \"{ FirstName: 'John',\n MiddleName: null,\n LastName: 'Doe'\n Address: '1234 Anywhere St.' }\" at path \"_id\"
Group.js (model file)
let UserSchema = mongoose.Schema({
FirstName: String,
MiddleName: String,
LastName: String,
Address: String
});
let User = mongoose.model(‘User', UserSchema);
let GroupSchema = mongoose.Schema({
name: type: String,
groupType: String,
users: [UserSchema]
});
module.exports = mongoose.model('Group', GroupSchema);
GroupRepository.js (method call that gets the group with their associated user lists)
findGroupPopulateUsers(paramList, cb){
this.group.findOne(paramList).populate('users').exec(function (err, entity) {
if (!err)
cb(err, entity);
else
cb(err);
});
}
Declaration for the group model before injecting into the GroupRepository class
const user = require('../models/Group');
Here is the paramList I am passing in to the findGroupPopulateUsers method call
{"groupType": "free"}
I am confused about what is triggering this error? I tried to follow the docs. on the mongoose website for handling subdocument population. Please let me know what I am missing in the rules about how subdocuments should be populated?
Here is a weird thing I am noticing, so the subdocument seems to return and I don't get the error if I don't specify the ".populate(....)" method call. However, if I try to access the "_id" field of the user subdocument, it keeps coming back undefined.
I think you should add .lean() for findOne function.
this.group.findOne(paramList).populate('users').lean().exec(function (err, entity) {
if (!err)
cb(err, entity);
else
cb(err);
});
Related
I'm trying to append an empty array in a mongo database with a String that is created in the front-end (website ui), the relevant code snipets are as following:
mongoose Schema
email: String,
displayName: String,
googleId: String,
toIgnore: [{toIgnoreURL: String}]
})
document creation with passport & passport-google-oauth20
User.findOne({email: email.emails[0].value}).then((currentUser)=>{
if(currentUser){
// user does already exist
console.log('welcome back!', currentUser)
done(null, currentUser)
}
else{ // user doesn't exist yet
new User({
email: email.emails[0].value,
displayName: email.displayName,
googleId: email.id,
toIgnore: []
}).save().then((newUser)=>{
console.log('new user created: ' + newUser)
done(null, newUser)
});
}
})
And lastly the attempt to append the toIgnore array property of the 'User' collection (of the currently logged in user)
User.update(
{email: emailThisSession},
{$push: {toIgnore: {toIgnoreURL: url}}})
In mongodb I see that the following document is successfully created
_id
:ObjectId(
IdOfDocumentInMongoDB)
toIgnore
:
Array
email
:
"myactualtestemail"
googleId
:
"longgoogleidonlynumbers"
__v
:
0
(Also as seen in attached image)
document in mongodb ui
I don't seem to figure out how to actually populate the 'toIgnore' array.
For instance, when console logging the following
var ignoreList = User.findOne({email:emailThisSession}).toIgnore;
console.log(ignoreList)
The output is undefined
Note that console logging the url variable does indeed print the value I want to append to the array!
I tried any format combination I could think of in the Schema builder and document creation, but I can't find figure the right way to get it done!
Any help would be appreciated!
Update, using promises didn't work either
User.findOne({email:emailThisSession}).then((currentUser)=>{ //adding .exec() after findOne({query}) does not help as in User.findOne({email:emailThisSession}).exec().then(...)
console.log(currentUser.toIgnore, url) //output is empty array and proper value for url variable, empty array meaning []
currentUser.toIgnore.push(url)
});
While adjusting the Schema as follows:
const userSchema = new Schema({
email: String,
displayName: String,
googleId: String,
toIgnore: []
})
Solution
I simply needed to change the update command to
User.updateOne(
{email: emailThisSession},
{$push: {toIgnore: {toIgnoreURL: url}}}).then((user)=>{
console.log(user)
})
Thanks #yaya!
Unable to add array element within a document with mongoose
define your schema as:
const UserSchema = new mongoose.Schema({
...
toIgnore: [{toIgnoreURL: String}]
})
then you can create an object like this :
new User({
...,
toIgnore: [] // <-- optional, you can remove it
})
To check the value:
User.findOne({...}).then(user => {
console.log(user.toIgnore)
});
Your update statement should be:
User.update(
{email: emailThisSession},
{$push: {toIgnore: {toIgnoreURL: url}}}
).then(user => {
console.log(user)
})
So in your case, this is undefined:
User.findOne({email:emailThisSession}).toIgnore
Since findOne is async. for getting the result, you can whether pass it a callback or you can use promises (User.findOne({...}).then(user => console.log(user.toIgnore)))
Update:
While adjusting the Schema as follows: new Schema({..., toIgnore: []})
Here is the problem with your update. you should change it back to : toIgnore: [{toIgnoreURL: String}]
I know there is allot's of answers about it but still I didn't quite get the idea.
I have CourseSchema:
const CourseSchema = new Schema({
course_name: String,
course_number: {type: String, unique : true },
enrolledStudents:[{
type: mongoose.Schema.Types.ObjectId,
ref: 'Student' }]
});
And a StudentSchema:
const StudentSchema = new Schema({
first_name: String,
last_name: String,
enrolledCourses:[{
type: mongoose.Schema.Types.ObjectId,
ref: 'CourseSchema'
}]
});
I want to reffer enrolledStudents at CourseSchema with a student, and enrolledCourses at StudentSchema with a course.
router.post('/addStudentToCourse', function (req, res) {
Course.findById(req.params.courseId, function(err, course){
course.enrolledStudents.push(Student.findById(req.params.studentId, function(error, student){
student.enrolledCourses.push(course).save();
})).save();
});
});
but when posting I get an error:
TypeError: Cannot read property 'enrolledStudents' of null
Ok so after readying Query-populate I did that:
router.post('/addStudentToCourse', function (req, res) {
Course.
findOne({ _id : req.body.courseId }).
populate({
path: 'enrolledStudents'
, match: { _id : req.body.studentId }
}).
exec(function (err, course) {
if (err) return handleError(err);
console.log('The course name is %s', course.course_name);
});
});
And when i'm hitting POST on postman I get on the console:
The course name is intro for cs
but it is loading for ever and later on console I get:
POST /courses/addStudentToCourse - - ms - -
You are missing the populate instruction. For example:
see more about it here
Course.
findOne({ courseId : req.params.courseId }).
populate('enrolledStudents').
exec(function (err, course) {
if (err) return handleError(err);
console.log('The course name is %s', course.name);
});
It is working by using the ref field that "knows" how to populate withput using the push syntax. it is like a foreign key population.
Just call the populate method on the query and an array of documents will be returned in place of the original _ids. you can learn more on the internals of the populate methods in the official docs
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);
});
I have the following schemas:
var reviewSchema = new mongoose.Schema({
comments : String,
rating : String,
submitted_date: {type: Date, default: Date.now},
numAgreed : Number,
numDisagreed : Number
});
var userSchema = new mongoose.Schema({
firstName : String,
lastName : String,
numRatings : Number,
averageRating: Number,
reviews : [reviewSchema]
});
I am implementing an agree function (increment number of those who agreed with the review) for every review as follows:
exports.processAgree = function(req,res){
var firstName = req.body.firstName;
var lastName = req.body.lastName;
var index = req.body.index;
User.findOne({firstName:firstName,lastName:lastName}).lean().exec(function(err,user) {
if (err) {
throw err;
}
else{
user.reviews[index].numAgreed++;
user.markModified('reviews');
user.save(function (err) {
if (err) throw err;
});
}
});
};
However, I get the error:
reviewedUser.markModified('reviews');
^
TypeError: Object #<Object> has no method 'markModified'
I searched through stackoveflow and have seen responses to this issue but they don't to work in my case. E.g. There was a response at How to update an embedded document within an embedded document in mongoose?
The solution suggests to declare child schemas before the parent schemas which is the case in my situation.
Please let me know if more information is required to help.
Thanks.
As Johnny said, you should remove the call to lean method on Query.
Such that your code would look like
User.findOne({firstName:firstName,lastName:lastName}).exec(function(err,user) {
if (err) {
throw err;
}
else{
user.reviews[index].numAgreed++;
user.markModified('reviews');
user.save(function (err) {
if (err) throw err;
});
}
});
Lean is used to strip all the service methods and properties from objects that come from Mongoose methods. If you don't use lean method on Query object, Mongoose will return instances of Model. Mongoose doc on lean().
And markModified method, that you are looking for, resides in Mongoose Model instance. By the way, save is in Model instance too.
I have a schema as follows (simplified):
var Permission = new Schema({
_id: String, // email address
role: String // "admin" or "member"
});
var Org = new Schema({
name: {type: String, index: {unique: true, dropDups: true}, trim: true},
permissions: [Permission]
});
An example document would look like this:
{
"name": "My Org",
"permissions" : [
{"_id" : "joe#gmail.com", "role" : "admin"},
{"_id" : "mary#gmail.com", "role" : "member"}
]
}
I am trying to delete one of the permissions rows, using the command org.permissions.remove(req.params.email), as shown in context below:
exports.removePermissions = function(req, res) {
var name = req.params.name;
return Org
.findOne({name: name})
.select()
.exec(function(err, org) {
if (err) return Org.handleError(res, err);
if (!org) return Org.handleError(res, new Error("#notfound " + name));
org.permissions.remove(req.params.email);
org.save(function(err, org) {
if (err) return Org.handleError(res, err);
else return res.send(org);
});
});
};
When I do this, I get the following error:
TypeError: Cannot use 'in' operator to search for '_id' in joe#gmail.com
at EmbeddedDocument.Document._buildDoc (/../node_modules/mongoose/lib/document.js:162:27)
at EmbeddedDocument.Document (/../node_modules/mongoose/lib/document.js:67:20)
at EmbeddedDocument (/../node_modules/mongoose/lib/types/embedded.js:27:12)
at new EmbeddedDocument (/../node_modules/mongoose/lib/schema/documentarray.js:26:17)
at MongooseDocumentArray._cast (/../node_modules/mongoose/lib/types/documentarray.js:62:10)
at Object.map (native)
at MongooseDocumentArray.MongooseArray.remove (/../node_modules/mongoose/lib/types/array.js:360:21)
at model.Org.methods.removePermissions (/../models/org.js:159:20)
The only thing I can think of is that Mongoose does not support _id fields that are not ObjectID's? This is strange, because I use these elsewhere in my code and it works fine (e.g. org.permissions.id("joe#gmail.com") works).
Any suggestions much appreciated!
I'm not sure why using remove there isn't working, but you can do this atomically with findOneAndUpdate and the $pull operator:
exports.removePermissions = function(req, res) {
var name = req.params.name;
return Org.findOneAndUpdate(
{name: name},
{$pull: {permissions: {_id: req.params.email}}},
function(err, org) {
// org contains the updated doc
...
});
};
As per this answer, you need to call remove() on the subdocument you want to remove, rather than on the entire subdocument array.
So, change:
org.permissions.remove(req.params.email);
to:
org.permissions.id(req.params.email).remove();
This two-step method has the added advantage over the answer supplied by #JohnnyHK in that you can validate whether the subdocument actually exists before removing it. This can be useful if you'd like to send a 404 response indicating that the subdocument doesn't exist - as far as I am aware, this isn't possible using the $pull atomic operator.
Note that this also will only work if your subdocument array has a schema, as illustrated in the question. If it doesn't, or it has a schema type of Mixed, the collection returned from the database will be a plain array rather than a Mongoose-enhanced array. This means that there is no .id() function. In this case, I would use lodash#remove instead:
_.remove(org.permissions, (function(permission) {
return permission._id.toString() === req.params.email;
}));