Cannot Read property '_id' of undefined while using mongoose findOneandUpdate - node.js

I am using mean.io to make sports event management system.I am trying to update a model Player with rest api and it throws this error from mongoose:
{ _id: 5411c79895d600440c698fa1,
email: 'abc#bcd.com',
name: 'James Bond',
username: 'james.bond',
games: [ 5411bd54786cfe2420f1e27a ],
teams: [],
roles: [ 'authenticated' ] }
[TypeError: Cannot read property '_id' of undefined]
PUT /api/players/5411c79895d600440c698fa1 500 199.402 ms - 36
I also tried to delete the _id proterty from player but it doesnot works either.
the method i have used to update the model player is :
exports.update = function(req, res) {
var player = req.player;
player = _.extend(player, req.body);
console.log(player);
Player.findOneAndUpdate(req.params.playerId, player,{new:true}, function (err, updatedPlayer) {
console.log(err);
if (err) {
return res.status(500).json({
error: 'Cannot update the player'
});
}
res.json(updatedPlayer);
});
And also if i used the model.save method provided in the default package article of mean.io, it shows another error. I have extended the user model in player package app.js file. So whenever I try to update one field, the field that I have declared in app.js are required and the path required error from mongoose is thrown.

You have two issues in your update request.
First, the findOneAndUpdate expects a dict as the query and not just the id, so you should give it {_id: req.params.playerId} instead.
Second, passing a mongoose object as the update data is risky, instead you should convert it to a dict like this var _player = player.toObject() and then have _player be passed to the update request. Remember that you need to remove the _id param of_player because you can't change the _id of a document. Before doing the update just do delete _player._id and you should be fine. Also, new is set to true by default so you won't need the options dict.
Here is your working code:
var player = _.extend(req.player, req.body);
var _player = player.toObject();
delete _player._id;
var query = {_id: req.params.playerId};
Player.findOneAndUpdate(query, _player, function (err, updatedPlayer) {
...
});
But in this case you shouldn't even have to do the first operation. Since you just want to update the data of req.body you can do:
var query = {_id: req.params.playerId};
var data = {
$set: req.body,
};
Player.findOneAndUpdate(query, data, function (err, updatedPlayer) {
...
});
This will update all the fields of the player matching the query with the values from req.body.

Related

Mongoose findById returns null even with valid id

I have already seen the discussion about the following question with a similar title
mongoose 'findById' returns null with valid id
But my problem is not the database name since all my other connections with the same database in fact the queries on the same collection are working fine.
I am using mongoose 4.13.6, node js 6.11 and mongo 3.4.
It is a post request .
var query=req.body;
I am sending the search parameters as
var findFruit =
{
_id:query._id
}
When I print my findFruit I get :
_id:'5a1cf77920701c1f0aafb85e'
The controller function for this is :
Fruit.findById(findFruit._id,function(err,fruit){
if( _.isNull(err) ){
var response = genRes.generateResponse(true,"found successfully");
callback(response);
}
else{
var response = genRes.generateResponse(false,"there occured some error : "+err);
callback(response);
}
})
I even tried find
Fruit.find(findFruit,function(err,fruit){
if( _.isNull(err) ){
var response = genRes.generateResponse(true,"found successfully");
callback(response);
}
else{
var response = genRes.generateResponse(false,"there occured some error : "+err);
callback(response);
}
})
The collection for sure has the entry under this id .
I went through this git issue as well https://github.com/Automattic/mongoose/issues/3079
Unfortunately I cannot downgrade mongoose as it might affect multiple other working functions.
Edit :
I tried creating ObjectId like :
var mongoose = require('mongoose');
var ObjectID = require('mongodb').ObjectID;
var objectId = new ObjectID();
// Convert the object id to a hex string
var originalHex = objectId.toHexString();
// Create a new ObjectID using the createFromHexString function
var newObjectId = new ObjectID.createFromHexString(query._id);
The model file :
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var FruitSchema = new Schema({
name : {type : String, unique : true},
color : {type : String}
});
module.exports = mongoose.model('Fruit', FruitSchema);
All my findById("id") calls returned null.
When looking at the collection with Compass I realized that all my _id elements were Strings. Note that the entire collection was imported.
I created a new element in Compass and the _id was created as ObjectId! and when I called findById("id") with that element's id it worked!
My conclusion is that there is obviously a bug with import. I have not found a way to convert the string _id fields to ObjectId's in the actual collection.
All my findById("id") calls returned null, when _id elements are Strings.
In the first place:
Check your mongodb database, if _id is stored as String, findById(id) can not find since it identifies ObjectId. If you've used import database by using mongoimport command and including _id in JSON:
Solution 1:
modify your JSON and for each document, change _id for instance:
_id: "5a68fde3f09ad7646ddec17e" to the following and run mongoimport again:
"_id": { "$oid": "5a68fde3f09ad7646ddec17e" }
Solution 2:
delete _id in the JSON file, drop collection and import again. Mongo will auto-create _id.
After any of solutions above, findById("id") will work.
Secondly:
Specifically in such cases where your _id elements are string, might be a better idea to use mongodb package: npm i mongodb
var MongoClient = require('mongodb').MongoClient;
var url = "mongodb://localhost:27017/";
MongoClient.connect(url, function (err, db) {
if (err) throw err;
var dbo = db.db("your_db_name");
dbo.collection("fruits_collection")
.find({_id:'5a1cf77920701c1f0aafb85e'})
//.find({_id:'5a1cf77920701c1f0aafb85e'}, { projection: { _id: 0, name: 1, color: 1} }) // to select specific columns desired
.toArray(function (err, result) {
if (err) throw err;
console.log(result);
db.close();
});
});
The above simple code, assumed you manage error handling yourself, either through try-catch, or
sending 404 as status code, or redirect to error page template, depending on whether the code is embedded in the Express route handler or not.
Hope this helped .. :)
Still trying to figure out why findById didn't work for me but the following piece of code did it
var mongoose = require('mongoose');
var newObjectId=new mongoose.Types.ObjectId(query._id);
var params={
'_id':newObjectId
}
Fruit.find(params).exec(function (err,fruit) {
if( _.isNull(err) ){
var response = genRes.generateResponse(true,"found successfully");
callback(response);
}
else{
var response = genRes.generateResponse(false,"there occured some error : "+err);
callback(response);
}
})
#Shoom. Yes, worked for me, thanks. findById() expects ObjectID, not a String.
I did not have a constraint to create documents with a specific id, so I imported with no _id. The db had newly-assigned _id as ObjectID.
findById(id), (and updateOne({ _id: id }, ...), started working as expected.

Mongoose Update Document or Insert New Document if ID Is Not Found (Express REST API)

I have set up a Express REST Api and would like to Implement a task that checks if a certain Object ID exists.
If that ID exists in the Mongo Database I want to update this Document using a Put Request. This Part already works in my code.
IF the ID does not yet exist in the database I would like to create a new Document. To do this I tried to set the "upsert" option to true, But If I now make a put request to a ID, which isn't in my DB yet it comes back with the error:
throw er; // Unhandled 'error' event
^
CastError: Cast to ObjectId failed for value "34455ab67" at path "_id" for model "event"
Here is my Code for the event model:
var mongoose = require('mongoose');
var eventSchema = mongoose.Schema({
name: {
type: String,
required: true
}
});
var Events = module.exports = mongoose.model('event', eventSchema, 'event');
module.exports.updateEvent = function (id, event, options, callback) {
var query = {_id : id};
var update = {
name: event.name,
};
Events.findOneAndUpdate(query, update, options, callback);
};
This is the REST API Call
app.put('/api/event/:_id', function(req, res) {
var id = req.params._id;
var event = req.body;
var options = {
upsert: true,
new: true,
setDefaultsOnInsert: true
};
Events.updateEvent(id, event, options, function(err, event){
if(err){
trow err;
}
res.json(event);
})
});
Any help would be highly appreciated!
This error happened because you defined the event schema without "_id",so the type of "_id" property by default become "ObjectId" and then when you tried to send "_id" with this value "34455ab67" mongoose method tried to cast it to "ObjectId" type but it's invalid string. so you should do the following:
Send valid string values for the "_id" property.
Validate the input string before converting it to "ObjectId".
If the input string is valid,convert it to "ObjectId" then use it in
the mongoose queries.
Check the following example:
var mongoose = require('mongoose');
//inputId is the input string passed from the front end
if (inputId.match(/^[0-9a-fA-F]{24}$/)) {
// it's a valid string to be converted to "ObjectId"
var id = mongoose.Types.ObjectId(inputId);
//then use this id in your mongoose queries.
} else {
//Not valid
}
or you can define the "_id" with "String" type to simplify this process but that depends on your needs.

Mongo / Express Query Nested _id from query string

Using: node/express/mongodb/mongoose
With the setup listed above, I have created my schema and model and can query as needed. What I'm wondering how to do though is, pass the express request.query object to Model.find() in mongoose to match and query the _id of a nested document. In this instance, the query may look something like:
http://domain.com/api/object._id=57902aeec07ffa2290f179fe
Where object is a nested object that exists elsewhere in the database. I can easily query other fields. _id is the only one giving an issue. It returns an empty array of matches.
Can this be done?
This is an example and not the ACTUAL schema but this gets the point across..
let Category = mongoose.Schema({
name: String
})
let Product = mongoose.Schema({
name: String,
description:String,
category:Category
})
// sample category..
{
_id:ObjectId("1234567890"),
name: 'Sample Category'
}
// sample product
{
_id:ObjectId("0987654321"),
name:'Sample Product',
description:'Sample Product Description',
category: {
_id:ObjectId("1234567890"),
name: 'Sample Category'
}
}
So, what I'm looking for is... if I have the following in express..
app.get('/products',function(req,res,next){
let query = req.query
ProductModel.find(query).exec(function(err,docs){
res.json(docs)
})
})
This would allow me to specify anything I want in the query parameters as a query. So I could..
http://domain.com/api/products?name=String
http://domain.com/api/products?description=String
http://domain.com/api/products?category.name=String
I can query by category.name like this, but I can't do:
http://domain.com/api/products?category._id=1234567890
This returns an empty array
Change your query to http://domain.com/api/object/57902aeec07ffa2290f179fe and try
app.get('/api/object/:_id', function(req, res) {
// req._id is Mongo Document Id
// change MyModel to your model name
MyModel.findOne( {'_id' : req._id }, function(err, doc){
// do smth with this document
console.log(doc);
});
});
or try this one
http://domain.com/api/object?id=57902aeec07ffa2290f179fe
app.get('/api/object', function(req, res) {
var id = req.param('id');
MyModel.findOne( {'_id' : id }, function(err, doc){
console.log(doc);
});
})
First of all increase your skills in getting URL and POST Parameters by this article.
Read official Express 4.x API Documentation
Never mind I feel ridiculous. It works just as I posted above.. after I fixed an error in my schema.

Object #<Promise> has no method 'limit'

when I run code
var collection = db.get('categories');
console.log(collection.find().limit(1).sort( { _id : -1 } ));
on nodejs using mongodb I am getting error Object # has no method 'limit' . I am a beginner to node and really stuck on this section of node
here is full code for geting last insert document.
router.post('/addcategory', function(req, res) {
// Set our internal DB variable
var db = req.db;
// Get our form values. These rely on the "name" attributes
var name = req.body.name;
var description = req.body.description;
// Set our collection
var collection = db.get('categories');
// Submit to the DB
collection.insert({
"name" : name,
"description" : description,
}, function (err, doc) {
if (err) {
// If it failed, return error
res.send("There was a problem adding the information to the database.");
}
else {
// And forward to success page
/******************/
console.log(collection.find().limit(1).sort( { _id : -1 } ));
/*************/
}
});
});
The key piece of missing information here was that you are using Monk, not the native MongoDB Node.JS driver. The command you have for find() is how you would use the native driver (with the changes suggested by #BlakesSeven above for asynchronity), but Monk works a little bit differently.
Try this instead:
collection.find({}, { limit : 1, sort : { _id : -1 } }, function (err,res) {
console.log(res);
});
The method is still asynchronous so you still need to invoke itm either as a promise with .then() or a callback. No methods are sychronous and return results in-line.
Also the result returned from the driver is s "Cursor" and not the object(s) you expect. You either iterate the returned cursor or just use .toArray() or similar to convert:
collection.find().limit(1).sort({ "_id": -1 }).toArray().then(function(docs) {
console.log(docs[0]);
});
Or:
collection.find().limit(1).sort({ "_id": -1 }).toArray(function(err,docs) {
console.log(docs[0]);
});
But really the whole premise is not correct. You seem to basically want to return what you just inserted. Event with the correction in your code, the returned document is not necessarily the one you just inserted, but rather the last one inserted into the collection, which could have occurred from another operation or call to this route from another source.
If you want what you inserted back then rather call the .insertOne() method and inspect the result:
collection.insertOne({ "name": name, "description": description },function(err,result) {
if (err) {
res.send("There was an error");
} else {
console.log(result.ops)
}
});
The .insert() method is considered deprecated, but basically returns the same thing. The consideration is that they return a insertWriteOpResult object where the ops property contains the document(s) inserted and their _id value(s),

Trying to remove a subdocument in Mongoose gives me an internal mongoose error

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

Resources