How to change the document using pre save in Mongoose - node.js

I am trying to assign the pre handler to the mongoose save event and encrypt the document before saving:
userShecma.pre('save', function(next) {
var self = {};
self.Key = this.password;;
self.EncriptedString = encrypt.encrypt(JSON.stringify(this), this.password);
self.user = this.user
self.decrypt = function() {
var user = JSON.parse(encrypt.decrypt(this.EncriptedString, this.Key));
for(var key in user) {
this[key] = user[key];
}
}
for(var key in this){
delete this[key];
}
for(var key in self){
this[key] = self[key];
}
console.log(this);
next(self);
});
I have tried a bunch of diffrent things, sometimes I get an error, sometimes it just doesnt change the document.
Let me know if you need any more info,
Ari
EDIT: Tried Benoir's Answer, I can't edit this.

I believe calling next(self) will make the next handler think that there was an error and not save the document.
You should just call next()
Look at http://mongoosejs.com/docs/middleware.html
under 'Error Handling'

I Figured It Out:
Benoir's Answer + You Cannot add or remove properties to/from the document unless they are defined in the Schema.

Related

generate hashed password on findOneAndUpdate [duplicate]

I'm trying to update counts on a pre hook. The issue is that for some unknown reason the findOneAndUpdate hook doesn't have access to the document, as far as I can tell.
I would like to do this:
source.pre('findOneAndUpdate', function (next) {
console.log('------------->>>>>> findOneAndUpdate: ');
this.objects = this.objects || [];
this.people = this.people || [];
this.events = this.events || [];
this.objectCount = this.objects.length;
this.peopleCount = this.people.length;
this.eventCount = this.events.length;
next();
});
But for some reason the this in the hook isn't the document, its a Query object which seems about useless.
What am I missing? How do I use a pre hook to update counts on a findOneAndUpdate?
You can do smthng like that ->
source.pre('findOneAndUpdate', function (next) {
console.log('------------->>>>>> findOneAndUpdate: ');
this._update.$set.objects = [];
this._update.$set.people = [];
this._update.$set.events = [];
next();
});
pay attention to _update.$set because in the context "this" will be a query. So you can easily add anything you want!
The documentation states:
Query middleware differs from document middleware in a subtle but important way: in document middleware, this refers to the document being updated. In query middleware, mongoose doesn't necessarily have a reference to the document being updated, so this refers to the query object rather than the document being updated.
An update action generally updates a document that only exists in the database (it tells the MongoDB server: "find document X and set property X to value Z"), so the full document isn't available to Mongoose and, hence, you can't update the counts (which requires access to at least the arrays whose length you want to determine).
As an aside: why do you need separate *Count properties in your schema anyway? If you want to query for arrays matching a certain size, you can use the $size operator on the arrays directly.
If you really do need the count properties, then for each update, you need to track the number of changes you made to each of the arrays (in terms of the number of items added/deleted) and use the $inc operator to adjust the counts.
I had a similar issue when I used the updateOne method and was also going to use the updateOne pre hook to make intermittent update before saving to the database. couldn't find a way for it to work. I ended up using the findOneAndUpdate pre hook and doing the updateOne in it.
schema.pre('findOneAndUpdate', async function(next){
const schema = this;
const { newUpdate } = schema.getUpdate();
const queryConditions = schema._condition
if(newUpdate){
//some mutation magic
await schema.updateOne(queryConditions, {newUpdate:"modified data"});
next()
}
next()
})
Another solution is to use the official MongoDB documentation on middleware. They explain why "this" does not refer to the document itself. You may try something in that sense:
source.pre('findOneAndUpdate', async function(next) {
const docToUpdate = await this.model.findOne(this.getFilter());
//modify the appropriate objects
docToUpdate.save(function(err) {
if(!err) {
console.log("Document Updated");
}
});
console.log(docToUpdate);
// The document that `findOneAndUpdate()` will modify
next();
});
This worked for me
SCHEMA.pre('findOneAndUpdate', function(next){
this._update.yourNestedElement
next();
});
schema.pre(['findOneAndUpdate'], async function(next) {
try {
const type = this.get('type')
const query = this.getQuery()
const doc = await this.findOne(query)
if (type) {
this.set('type', doc.type)
}
next()
} catch (e) {
next(new BaseError(e))
}
})
mongoose Documentation:
You cannot access the document being updated in pre('updateOne') or pre('findOneAndUpdate') query middleware. If you need to access the
document that will be updated, you need to execute an explicit query
for the document.
schema.pre('findOneAndUpdate', async function() {
const docToUpdate = await this.model.findOne(this.getQuery());
console.log(docToUpdate); // The document that `findOneAndUpdate()` will modify
});

Search Value Firebase Node.js

I am trying to access a key with unknown parent as :
var ref = admin.database().ref("user_contacts/{pushId}").child(event.data.ref.parent.key);
ref.once("value", function(querySnapshot) {
var qVal = querySnapshot.val();
console.log("Query Value:", qVal);
});
Snapshot always return null, data exists for this key under multiple.
{pushId} //push ids are unknown here.
I also tried this:
var ref = admin.database().ref("user_contacts/{pushId}").orderByChild(snapshot.ref.parent.key);
ref.once("value", function(querySnapshot) {
var qVal = querySnapshot.val();
console.log("Query Value:", qVal);
});
but same response.
Is there any else way to achieve this? Or any body here can suggest an improvement in my method? I am just trying to search a key in the database.

How to cache a mongoose query in memory?

I have the following queries, which starts with the GetById method firing up, once that fires up and extracts data from another document, it saves into the race document.
I want to be able to cache the data after I save it for ten minutes. I have taken a look at cacheman library and not sure if it is the right tool for the job. what would be the best way to approach this ?
getById: function(opts,callback) {
var id = opts.action;
var raceData = { };
var self = this;
this.getService().findById(id,function(err,resp) {
if(err)
callback(null);
else {
raceData = resp;
self.getService().getPositions(id, function(err,positions) {
self.savePositions(positions,raceData,callback);
});
}
});
},
savePositions: function(positions,raceData,callback) {
var race = [];
_.each(positions,function(item) {
_.each(item.position,function(el) {
race.push(el);
});
});
raceData.positions = race;
this.getService().modelClass.update({'_id' : raceData._id },{ 'positions' : raceData.positions },callback(raceData));
}
I have recently coded and published a module called Monc. You could find the source code over here. You could find several useful methods to store, delete and retrieve data stored into the memory.
You may use it to cache Mongoose queries using simple nesting as
test.find({}).lean().cache().exec(function(err, docs) {
//docs are fetched into the cache.
});
Otherwise you may need to take a look at the core of Mongoose and override the prototype in order to provide a way to use cacheman as you original suggested.
Create a node module and force it to extend Mongoose as:
monc.hellocache(mongoose, {});
Inside your module you should extend the Mongoose.Query.prototype
exports.hellocache = module.exports.hellocache = function(mongoose, options, Aggregate) {
//require cacheman
var CachemanMemory = require('cacheman-memory');
var cache = new CachemanMemory();
var m = mongoose;
m.execAlter = function(caller, args) {
//do your stuff here
}
m.Query.prototype.exec = function(arg1, arg2) {
return m.execAlter.call(this, 'exec', arguments);
};
})
Take a look at Monc's source code as it may be a good reference on how you may extend and chain Mongoose methods
I will explain with npm redis package which stores key/value pairs in the cache server. keys are queries and redis stores only strings.
we have to make sure that keys are unique and consistent. So key value should store query and also name of the model that you are applying the query.
when you query, inside the mongoose library, there is
function Query(conditions, options, model, collection) {} //constructor function
responsible for query. inside this constructor,
Query.prototype.exec = function exec(op, callback) {}
this function is responsible executing the queries. so we have to manipulate this function and have it execute those tasks:
first check if we have any cached data related to the query
if yes respond to request right away and return
if no we need to respond to request and update our cache and then respond
const redis = require("client");
const redisUrl = "redis://127.0.0.1:6379";
const client = redis.createClient(redisUrl);
const util = require("util");
//client.get does not return promise
client.get = util.promisify(client.get);
const exec = mongoose.Query.prototype.exec;
//mongoose code is written using classical prototype inheritance for setting up objects and classes inside the library.
mongoose.Query.prototype.exec = async function() {
//crate a unique and consistent key
const key = JSON.stringify(
Object.assign({}, this.getQuery(), {
collection: this.mongooseCollection.name
})
);
//see if we have value for key in redis
const cachedValue = await redis.get(key);
//if we do return that as a mongoose model.
//the exec function expects us to return mongoose documents
if (cachedValue) {
const doc = JSON.parse(cacheValue);
return Array.isArray(doc)
? doc.map(d => new this.model(d))
: new this.model(doc);
}
const result = await exec.apply(this, arguments); //now exec function's original task.
client.set(key, JSON.stringify(result),"EX",6000);//it is saved to cache server make sure capital letters EX and time as seconds
};
if we store values as array of objects we need to make sure that each object is individullay converted to mongoose document.
this.model is a method inside the Query constructor and converts object to a mongoose document.
note that if you are storing nested values instead of client.get and client.set, use client.hset and client.hget
Now we monkey patched
Query.prototype.exec
so you do not need to export this function. wherever you have a query operation inside your code, mongoose will execute above code

Blocking node from reading simultaneously from db

I have a set of values in a mongoDB I need to only be read ones. So when I have read them I delete that line from the DB. But since node is async if I do getValue() twice in quick succession I get the same value, since the DB has not had time to delete the old one. Does anyone know a good way to fix this problem. I can think of a couple but nothing good.
I don’t have my code here so just wrote a quick example to show my problem.
Var getValue = function() {
ReadfromDB(function(data){
deleteRecord(); // show that what we read has been updated
});
}
You could try something like this. The reading is placed into a promise that's done when the deleting is done as well. This can stack so if you do getValue() 20 times it still should execute in the correct order.
var getValue = function(){
var currentPromise;
var read = function(){
return ReadfromDB().then(function(data){
deleteRecord(); // show that what we read has been updated
});
}
return function() {
if(currentPromise){
currentPromise = currentPromise.then(read)
}else{
currentPromise = read();
}
}
}
What you need to do outside this code is make sure ReadfromDB returns a promise object.
Sounds like a good use case for a closure!
var getValue = (function() {
var called = false;
return function() {
if (!called) {
called = true;
ReadFromDB(function(data) {
deleteRecord();
});
}
};
}());
You could also use once to do exactly the same thing.
var once = require('once');
var getValue = once(function() {
ReadFromDB(function(data) {
deleteRecord();
});
});

Get a collection and add a value to the response

I want to create in the Server script a function that can return a collection plus some extra value.
For example:
Meteor.publish("users", function () {
var users;
users = Meteor.users.find();
users.forEach(function (user){
user.profile.image = "some-url";
});
return users;
});
But this don't work proper. My question is: What is the right way to add a value to a collection reponse in a publish function.
There are 2 ways you can implement a publish function:
By returning a cursor (or an array of cursors)
By using this.added(), this.changed() and this.removed().
Only method 2 allows to modify returned documents.
Please refer to Meteor documentation here. However, since the provided sample code might look complex, here is another one:
// server: publish the rooms collection
Meteor.publish("rooms", function () {
return Rooms.find({});
});
is equivalent to:
// server: publish the rooms collection
Meteor.publish("rooms", function () {
var self = this;
var handle = Rooms.find({}).observeChanges({
added: function(id, fields) { self.added("rooms", id, fields); },
changed: function(id, fields) { self.changed("rooms", id, fields); },
removed: function(id) { self.added("rooms", id); },
}
});
self.ready();
self.onStop(function () { handle.stop(); });
});
In the second sample, you can modify the 'field' parameter before sending it for publication, like this:
added: function(id, fields) {
fields.newField = 12;
self.added("rooms", id, fields);
},
Source: this post.
Is this important to do with the server? You could use the transform function on the client:
Client JS
//Somewhere where it can run before anything else (make sure you have access to the other bits of the document i.e services.facebook.id otherwise you'll get a services is undefined
Meteor.users._transform = function(doc) {
doc.profile.image = "http://graph.facebook.com/" + doc.services.facebook.id + "/picture";
return doc;
}
Now when you do:
Meteor.user().profile.image
=> "http://graph.facebook.com/55592/picture"
I have opened an issue before with regards to sharing a transform onto the client: https://github.com/meteor/meteor/issues/821

Resources