"Hook" to Save function Globally - something like post event - node.js

I want to keep track(by loging to another collection) on any Create , Update or Delete operation that happen in Mongodb.
My existing app (NodeJs) uses mongoose for repository. I'm trying to log every time something has changed(CUD) in the DB. Simple solution would be to add logs everywhere i modify to db, but i have so many methods.I wonder if i can "hook" on a post Save globally? something like middleware to Mongo.

Use below hook on schema on which you want to log(this is only for save function)
schema.post('save', function(next){
//log data using this here
next();
})
Refer to this link:
http://mongoosejs.com/docs/middleware.html
as per this link:
> use myDb
switched to db myDb
> db.getProfilingLevel()
0
> db.setProfilingLevel(2)
{ "was" : 0, "slowms" : 1, "ok" : 1 }
> db.getProfilingLevel()
2
> db.system.profile.find().pretty()
reference: MongoDB logging all queries

Related

Updating Particular field in Nodejs

I wanted to update the field data only ,but my code it adding an object each time i am calling update api.I have gone through many sites and found out updateOne is the method but couldnt end up undersatnding how to implement here.I am quite new to node so any help would be appreciated.
const update=(req,res)=>{
console.log(req);
models.detailsSchema.findByIdAndUpdate(req.body.vehicleId,req.body.data,{new:true}).then((msg,err)=>{
if(err)
res.status(400).json({err})
res.status(200).json({
"resCode":"0000",
"resStatus":"Success",
"resMsg":msg
})
});
}
Looks like you're using Mongoose connected to a MongoDB instance? If that's the case, Schema.findByIdAndUpdate works on the primary key or ObjectId of the record you're trying to update. To make this code work, if my assumptions are correct, change to this:
models.detailsSchema.findByIdAndUpdate(req.body._id, req.body.data, { new:true })
Of course, you're going to want to put in some check to make sure _id is defined if this is a create/update route.

Concurrentcy update in mongodb

I have used this code on node.js server side to update multiple embedded documents.
DetailerItemGroupModel.find({
"_id": itemGroupId
})
.forEach(function (doc) {
doc.Products.forEach(function (ch) {
//do something before update
});
DetailerItemGroupModel.save(doc);
});
I have the scenario like this :
Client A and B do GET http Method to get the same documents in the same time, then client B doing the update first(call to server which will run the code above) and then client A do the update.
So I want when client A's doing the UPDATE HTTP method to the server they must get the data which was latest (in this case is the document was updated by B) , I mean some how to cancel Client A request and return a bad request to tell the data Client A's going to update was changed by another client. Any way to implement that?
I read about "__v", but not sure when Client A and B send request to update same document at the same time, Does it work with forEach(), I change the code to this
DetailerItemGroupModel.find({
"_id": itemGroupId,
"__v" : {document version}
})
.forEach(function (doc) {
doc.Products.forEach(function (ch) {
//do some thing before update
});
DetailerItemGroupModel.save(doc);
});
The idea is that you want your "Save" to only work on the version that you have (which should be the latest).
Using .save will not help you here.
You can use one of two functions:
update.
findOneAndUpdate
The Idea is you have to do the find and save in one Atomic operation. And in the conditions object, you not only send the _id but you also send the __v which is the version identifier. (Something similar to the below example)
this.update({_id: document._id, __v: document.__v}, {$set: document, $inc: {__v: 1}}, result.cb(cb, function(updateResult) {
...
});
Say you read version 10, now you are ready to update it (save it). It will only work if it finds the document with the particular id and version. If in the meantime the database gets a newer version (version 11) then your update will fail.
BTW, MongoDb now has Transactions, you might want to look into that, because a transaction B would make your code wait until transaction A finishes it's atomic operation, which would be a better model than doing an update and failing and then trying again...

Insert or update multiple documents in MongoDB

I want to implement hashtags functionality with NodeJS and MongoDB support, so that I can also count the uses. Whenever a user adds hashtags to a page, I want to push or update them in the database. Each hastag looks like this:
{_id:<auto>, name:'hashtag_name', uses: 0}
The problem I'm facing is that the user can add new tags as well, so when he clicks 'done', I have to increment the 'uses' field for the existing tags, and add the new ones. The trick is how to do this with only one Mongo instruction? So far I thought of 2 possible ways of achieving this, but I'm not particularly happy with either:
Option 1
I have a service which fetches the existing tags from the db before the user starts to write a new article. Based on this, I can detect which tags are new, and run 2 queries: one which will add the new tags, and another which will update the existing one
Option 2
I will send the list of tags to the server, and there I will run a find() for every tag; if I found one, I'll update, if not, I'll create it.
Option 3 (without solution for now)
Best option would be to run a query which takes an array of tag names, do a $inc operation for the existing ones, and add the missing ones.
The question
Is there a better solution? Can I achieve the end result from option #3?
You should do something like this, all of them will be executed in one batch, this is only an snippet idea how to do it:
var db = new Db('DBName', new Server('localhost', 27017));
// Establish connection to db
db.open(function(err, db) {
// Get the collection
var col = db.collection('myCollection');
var batch = col.initializeUnorderedBulkOp();
for (var tag in hashTagList){
// Add all tags to be executed (inserted or updated)
batch.find({_id:tag.id}).upsert().updateOne({$inc: {uses:1}});
}
batch.execute(function(err, result) {
db.close();
});
});
I would use the Bulk method offered by Mongodb since version 2.6. In the same you could perform insertion operations when the tag is new and the counter update when it already exists.

In mongoose pre middleware, how do i access the update query?

I am trying to use the new unstable version of mongoose >4.0.0 to validate update queries.
say that i want to update a schema using the following query
schema.update({_id:'blah'},{a:'blah'},function(err){
//do your thing
})
so lets say i have the following schema,
var schema = new Schema({
a:{type:String}
});
schema.pre('update',function(next){
var findQuery=this._conditions; // gives {_id:'blah'}
// how do i get {a:'blah'}????
next();
});
how do i get the update query of {set:{a:'blah'}} in the pre middleware so i can do some checks before executing the update?
alternatively i know that the update query can be accessed in the post middleware, in
schema.post('update',function(){
var findQuery=this._conditions; // gives {_id:'blah'}
var updateQuery=this._update; //gives {$set:{a:'blah'}}
next();
});
but thats too late, i need this in the pre middleware to check before actually updating the db.
i tried looking through the 'this' object of the pre middleware but cannot find the updateQuery object anywhere and this._update is undefined in the pre middleware.
Is there a way to do this?
thanks
In case you're still looking for a solution that works on array operations, it looks like in newer versions of mongoose (at least 4.0.7+), this._update is defined in the pre-middleware.
I found a work around through this particular example, however it doesnt quite solve my actual problem. what you can do in mongoose version ~4.0.0 is to let the pre middleware specify to go through the model validation on update.
schema.pre('update',function(next){
this.options.runValidators = true; // make sure any changes adhere to schema
})
basically, you can then specify the validators inside the schema
var schema = new Schema({
a:{
type:String,
validate:[...] //the validation you want to run
}
});
you can choose to skip the validation on a normal save operation by using the this.isNew check inside validation functions.
this code will run validate:[...] on any $set and $unset to a in your update query.
however, it doesn't work on array operations like $push or $addToSet for some reason. so if your updating an array, it won't run the validation code at all! hence it doesn't solve the actual problem im faced with. but it can work with the example provided for anyone that comes across this particular problem

Mongo Database Fixing Names after Mongoose Index?

I had a very weird issue with the way Mongoose interacted with my Node and Mongo database.
I was using express to create a basic get api route to fetch some data from my mongodb.
I had a database called test and it had a collection call "billings"
so the schema and route was pretty basic
apiRouter.route('/billing/')
.get(function(req, res) {
Billing.find(function(err, billings) {
if (err) res.send(err);
// return the bills
res.json(billings);
});
});
Where "Billing" was my mongoose schema. that simply had 1 object {test: string}
This worked fine, I got a response with all the items in my mongo db called "billings" which is only one item {test: "success"}
Next I created a collection called "historys"
I setup the exact same setup as my billings.
apiRouter.route('/historys/')
// get all the history
.get(function(req, res) {
Historys.find(function(err, historys) {
if (err) res.send(err);
// return the history
res.json(historys);
});
});
where again "Historys" was my mongoose schema. This schema was identical in setup to my billings since I didnt have any real data, the fields were the same, i just had it with a test field so the json object returned from both billings and historys should have been
{ test: "success" }
However, this time I didnt get any data back, I just got an empty object
[].
I went through my code multiple times to make sure maybe a capital got lost, or a comma somewhere etc, but the code was identical. the setup and formatting in my mongodb was identical. I went into robomongo and viewed the database and everything was named correctly.
Except, I had 2 new collections now.
My original : "Historys" AND a brand new collection "Histories"
Once i fixed my api route to go look at Histories instead of Historys, I was able to get the test data successfully. I still however cannot pull data from Historys, its like it doesnt exist yet there it was in my robomongo console when I refreshed.
I searched all my code for any mention of histories and got 0 results. Where did the system know to fix the grammar on my collection?
From the docs:
When no collection argument is passed, Mongoose produces a collection name by passing the model name to the utils.toCollectionName method. This method pluralizes the name. If you don't like this behavior, either pass a collection name or set your schemas collection name option.
So, when you did, in your schema definition, this:
mongoose.model('Historys', YourSchema);
, mongoose created the Histories collection.
When you do:
db.historys.insert({ test: "success" })
through mongodb console, if the historys collection doesn't exist, it'll be created. That's why you have the two collections in your db. Like the docs said, if you don't want mongoose to create a collection with a pluralized name based on your model, just specify the name you want.

Resources