findAndModify or findOneAndUpdate - "is not a function" - node.js

First of all, which one is best?
findAndModify or findOneAndUpdate or findByIdAndUpdate?
In my case I got a table like this:
seqkeys
{
"_id" : "invoice",
"seq" : NumberInt(1)
},
{
"_id" : "receipt",
"seq" : NumberInt(1)
}
And I want to find the seq number for invoice, add 1 and save. (and then use that number to save the real invoice record in the invoice table)
But I cant get this to work, I constantly get
TypeError: seqkeysTable.findAndModify is not a function
So here is my model:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var seqkeysSchema = new Schema({
_id: String, // Table Name
seq: Number // Sequence Number
});
module.exports = mongoose.model('seqkeysModel', seqkeysSchema,'seqkeys');
And here is my findkey nodejs function....
var seqkeysModel = require('../../models/seqkeysModel');
var seqkeysTable = mongoose.model('seqkeysModel');
var findKey = function(tableName) {
return new Promise((resolve, reject) => {
seqkeysTable.findAndModify(
{ "_id": tableName },
{ $inc: {"seq":1} },
{ new: true }
,function (err, data) {
if (err) {
reject(new Error('findKey ERROR : ' + err));
return;
} else {
resolve(data.seq);
};
});
})};

Mongoose has findOneAndUpdate (equivalent of mongoBD findAndModify. MongoDB also introduced findOneAndUpdate in version >= 3.2) and findByIdAndUpdate. That's why findAndModify won't work with Mongoose.
As to which one is better, in findOneAndUpdate you can pass query object containing multiple fields while findByIdAndUpdate only takes id as the first parameter.

findAndModify is a function of MongoDB, That's why that doesn't work with Mongoose.
Both findOneAndUpdate and findByIdAndUpdate works well with Mongoose, perhaps if you wish to use _id to search in the database then go for findByIdAndUpdate else if you are using a different field to search in the database then use findOneAndUpdate.
However, if you try to use findAndModify in MongoDB, you might get the following warning :
DeprecationWarning: collection.findAndModify is deprecated. Use findOneAndUpdate, findOneAndReplace or findOneAndDelete instead.
To eliminate this use the following in your code :
mongoose.set('useFindAndModify', false);
Just in case, difference between findByIdAndUpdate & findOneAndUpdate :
findByIdAndUpdate(id, ...) is equivalent to findOneAndUpdate({ _id: id
}, ...)

Related

Mongoose deleteMany using _id on nodejs

i have a ids of array ['123','456', '789']. i want delete all this array in mongodb
How i user it:
ScheduleModel.deleteOne({ _id: ['123','456', '789'] });
this is not working because this is not object Id
what i Need :
ScheduleModel.deleteOne({ _id: [ObjectId('123'), ObjectId('456'), ObjectId('789')] });
How to add object Id in array data. any how to resolve this issues. i need a solution on this.
It's not about ObjectId. you are using wrong syntax. You must use $in statement
ScheduleModel.deleteMany({ id: { $in: ['123','456','789'] } });
Use deleteMany with proper syntax.When there are many objectId at time Use $in.
Try this :
var deleteCondition = {
_id : {
//In Array you can pass your objectId
$in : ['123','456','789']
}
//You can pass other conditions
}
//deleteMany
ScheduleModel.deleteMany(deleteCondition, function (err, res) {
if (res) console.log(res)
})

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.

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

What is the equivalent of Model.visits.$inc() (Mongoose 2.7.x) in the current (3.8.x) version?

Mongooose ODB docs from 2.7.x say the following is valid:
Model.findOne({ name: 'borne' }, function (err, doc){
doc.name = 'jason borne';
doc.visits.$inc();
doc.save();
});
That was taken from mongoosejs.com (very bottom of page) for version 2.7.x. I'm learning Node.js right now and using Mongoose and the above doesn't seem to work.
I have a schema where I have a field with mongoose set to:
visits : Number
and on an object by default I set the visits = 0;
When I try something like:
Model.findOne({'email' : req.query.email}, function(error, mdl) {
mdl.visits.$inc();
mdl.save();
}
I get the following error in console:
TypeError: Object 0 has no method '$inc' ?
Any suggestions?
Thanks.
Those number extensions were causing problems and have been removed in Mongoose 3.x.
This is better handled by an atomic update with the $inc operator anyway:
Model.update(
{email: req.query.email},
{$inc: {visits: 1}},
function(error, numAffected) { ... }
);

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