SEE UPDATED EXAMPLE CODE @ BOTTOM
I'm using Mongoose (which is awesome btw!) in my current NodeJS projects, and I have a MDB collection thats going to store the changes of documents in a different collection (Basically a changelog storing what was modified)
How I'm trying to accomplish that is create a function that stores a JSON version of the document, which is done via the pre('save')
hook. Then create another hook, which gets executed via post('save')
, to compare the data stored in pre('save')
, and compare it with the documents new data.
Heres what I have thus far:
var origDocument
var testVar = 'Goodbye World'
module.exports = ( schema, options ) => {
schema.pre( 'save', function( next ) {
// Store the original value of the documents attrCache.Description value
origDocument = this.toJSON().attrCache.Description
// Change the testVar value to see if the change is reflected in post(save)
testVar = 'Hello World'
next()
} )
schema.post( 'save', function( ) {
// Attempt to compare the documents previous value of attrCache.Description, with the new value
console.log("BEFORE:", origDocument)
console.log("AFTER:", this.toJSON().attrCache.Description)
// Both of the above values are the same! >.<
console.log('post(save):',testVar) // result: post(save):Hello World
// But the above works just fine..
} )
}
I originally didn't think this would work. To test that the two hooks get executed in the same scope, I created a test variable at the top of the page called testVar
with some arbitrary value, then in the post(save)
hook, retrieved the testVar
, and the value modification of that variable was seen in the post save hook.
So from there, I just stored the value of this.toJSON()
in a variable, then in the post(save) hook, I am trying to retrieve the cached version of this document, and compare it to this.toJSON()
. However, it doesn't look like the document from the pre(save)
doesnt hold the pre-modified data, it somehow has the value of the document after it was updated.
So why can I update the value of testVar
from within a pre(save)
hook, and that change is reflected from a post(save)
hook function, but I cant do the same thing with the document itself?
Is what im trying to do here even possible? If so, what am I doing wrong? If not - How can I accomplish this?
Thank you
Update
Per the advice from @Avraam, I tried to run the data through JSON.stringify()
before saving it in memory via the pre(save)
hook, then do the same in the post(save)
, like so:
var origDocument
module.exports = ( schema, options ) => {
schema.pre( 'save', function( next ) {
origDocument = JSON.stringify( this.toJSON().attributes[1].value )
// Should store and output the CURRENT value as it was before the
// document update... but it displays the NEW value somehow
console.log( '[MIDDLEWARE] ORIGINAL value:', origDocument )
next()
} )
schema.post( 'save', function( ) {
var newDocument = JSON.stringify(this.toJSON().attributes[1].value)
console.log( '[MIDDLEWARE] UPDATED value:', newDocument )
} )
}
And here's the script that updates the mongoose document:
Asset.getAsset( '56d0819b655baf4a4a7f9cad' )
.then( assetDoc => {
// Display original value of attribute
console.log('[QUERY] ORIGINAL value:', assetDoc.attributes[1].value)
var updateNum = parseInt( assetDoc.__v )+1
assetDoc.attr('Description').set('Revision: ' + updateNum )
return assetDoc.save()
} )
.then(data => {
// Display the new value of the attribute
console.log('[QUERY] UPDATED value:', data.attributes[1].value)
//console.log('DONE')
})
.catch( err => console.error( 'ERROR:',err ) )
Heres the console output when I run the New script:
[QUERY] ORIGINAL value: Revision: 67
[MIDDLEWARE] ORIGINAL value: "Revision: 68"
[MIDDLEWARE] UPDATED value: "Revision: 68"
[QUERY] UPDATED value: Revision: 68
As you can see, the [QUERY] ORIGINAL value and the [QUERY] UPDATED values show that there was an update. But the [MIDDLEWARE] original/updated values are still the same... So im still stuck as to why
UPDATE
I figured maybe I could provide a more simplified but detailed example.
Heres the middleware module thats supposed to compare the pre(save)
and
post(save)
:
'use strict'
import _ from 'moar-lodash'
import * as appRoot from 'app-root-path'
import Mongoose from 'mongoose'
import diff from 'deep-diff'
var originalDesc
module.exports = ( schema, options ) => {
schema.pre( 'save', function( next ) {
originalDesc = JSON.parse( JSON.stringify( this.toJSON() ) ).attributes[1].value
console.log( '[MIDDLEWARE ORIGINAL Desc]\n\t', originalDesc )
next()
} )
schema.post( 'save', function( ) {
var newDesc = JSON.parse( JSON.stringify( this.toJSON() ) ).attributes[1].value
console.log( '[MIDDLEWARE NEW Desc]\n\t', newDesc)
} )
}
Then heres the the code that uses the Asset
model and updates the Description
attribute...
'use strict'
import _ from 'moar-lodash'
import Promise from 'bluebird'
import Mongoose from 'mongoose'
import Async from 'async'
import Util from 'util'
import * as appRoot from 'app-root-path'
Mongoose.Promise = Promise
Mongoose.connect( appRoot.require('./dist/lib/config').database.connection )
const accountLib = appRoot.require('./dist/lib/account')
const models = require( '../models' )( Mongoose )
models.Asset.getAsset( '56d0819b655baf4a4a7f9cad' )
.then( assetDoc => {
var jqDoc = JSON.parse(JSON.stringify(assetDoc.toJSON()))
// Show the CURRENT description
console.log('[IN QUERY - Before Modify]\n\t', jqDoc.attributes[1].value)
assetDoc.attr('Description').set( 'Date-'+Date.now() )
return assetDoc.save()
} )
.then(data => {
// Just show the Description AFTER it was saved
console.log('[AFTER QUERY - AFTER Modify]\n\t', data.attributes[1].value)
})
.catch( err => console.error( 'ERROR:',err ) )
.finally( () => {
Mongoose.connection.close()
console.log('# Connection Closed')
})
[IN QUERY - Before Modify]
Date-1474915946697
[MIDDLEWARE ORIGINAL Desc]
Date-1474916372134
[MIDDLEWARE NEW Desc]
Date-1474916372134
[AFTER QUERY - AFTER Modify]
Date-1474916372134
# Connection Closed
SEE UPDATED EXAMPLE CODE @ BOTTOM
I'm using Mongoose (which is awesome btw!) in my current NodeJS projects, and I have a MDB collection thats going to store the changes of documents in a different collection (Basically a changelog storing what was modified)
How I'm trying to accomplish that is create a function that stores a JSON version of the document, which is done via the pre('save')
hook. Then create another hook, which gets executed via post('save')
, to compare the data stored in pre('save')
, and compare it with the documents new data.
Heres what I have thus far:
var origDocument
var testVar = 'Goodbye World'
module.exports = ( schema, options ) => {
schema.pre( 'save', function( next ) {
// Store the original value of the documents attrCache.Description value
origDocument = this.toJSON().attrCache.Description
// Change the testVar value to see if the change is reflected in post(save)
testVar = 'Hello World'
next()
} )
schema.post( 'save', function( ) {
// Attempt to compare the documents previous value of attrCache.Description, with the new value
console.log("BEFORE:", origDocument)
console.log("AFTER:", this.toJSON().attrCache.Description)
// Both of the above values are the same! >.<
console.log('post(save):',testVar) // result: post(save):Hello World
// But the above works just fine..
} )
}
I originally didn't think this would work. To test that the two hooks get executed in the same scope, I created a test variable at the top of the page called testVar
with some arbitrary value, then in the post(save)
hook, retrieved the testVar
, and the value modification of that variable was seen in the post save hook.
So from there, I just stored the value of this.toJSON()
in a variable, then in the post(save) hook, I am trying to retrieve the cached version of this document, and compare it to this.toJSON()
. However, it doesn't look like the document from the pre(save)
doesnt hold the pre-modified data, it somehow has the value of the document after it was updated.
So why can I update the value of testVar
from within a pre(save)
hook, and that change is reflected from a post(save)
hook function, but I cant do the same thing with the document itself?
Is what im trying to do here even possible? If so, what am I doing wrong? If not - How can I accomplish this?
Thank you
Update
Per the advice from @Avraam, I tried to run the data through JSON.stringify()
before saving it in memory via the pre(save)
hook, then do the same in the post(save)
, like so:
var origDocument
module.exports = ( schema, options ) => {
schema.pre( 'save', function( next ) {
origDocument = JSON.stringify( this.toJSON().attributes[1].value )
// Should store and output the CURRENT value as it was before the
// document update... but it displays the NEW value somehow
console.log( '[MIDDLEWARE] ORIGINAL value:', origDocument )
next()
} )
schema.post( 'save', function( ) {
var newDocument = JSON.stringify(this.toJSON().attributes[1].value)
console.log( '[MIDDLEWARE] UPDATED value:', newDocument )
} )
}
And here's the script that updates the mongoose document:
Asset.getAsset( '56d0819b655baf4a4a7f9cad' )
.then( assetDoc => {
// Display original value of attribute
console.log('[QUERY] ORIGINAL value:', assetDoc.attributes[1].value)
var updateNum = parseInt( assetDoc.__v )+1
assetDoc.attr('Description').set('Revision: ' + updateNum )
return assetDoc.save()
} )
.then(data => {
// Display the new value of the attribute
console.log('[QUERY] UPDATED value:', data.attributes[1].value)
//console.log('DONE')
})
.catch( err => console.error( 'ERROR:',err ) )
Heres the console output when I run the New script:
[QUERY] ORIGINAL value: Revision: 67
[MIDDLEWARE] ORIGINAL value: "Revision: 68"
[MIDDLEWARE] UPDATED value: "Revision: 68"
[QUERY] UPDATED value: Revision: 68
As you can see, the [QUERY] ORIGINAL value and the [QUERY] UPDATED values show that there was an update. But the [MIDDLEWARE] original/updated values are still the same... So im still stuck as to why
UPDATE
I figured maybe I could provide a more simplified but detailed example.
Heres the middleware module thats supposed to compare the pre(save)
and
post(save)
:
'use strict'
import _ from 'moar-lodash'
import * as appRoot from 'app-root-path'
import Mongoose from 'mongoose'
import diff from 'deep-diff'
var originalDesc
module.exports = ( schema, options ) => {
schema.pre( 'save', function( next ) {
originalDesc = JSON.parse( JSON.stringify( this.toJSON() ) ).attributes[1].value
console.log( '[MIDDLEWARE ORIGINAL Desc]\n\t', originalDesc )
next()
} )
schema.post( 'save', function( ) {
var newDesc = JSON.parse( JSON.stringify( this.toJSON() ) ).attributes[1].value
console.log( '[MIDDLEWARE NEW Desc]\n\t', newDesc)
} )
}
Then heres the the code that uses the Asset
model and updates the Description
attribute...
'use strict'
import _ from 'moar-lodash'
import Promise from 'bluebird'
import Mongoose from 'mongoose'
import Async from 'async'
import Util from 'util'
import * as appRoot from 'app-root-path'
Mongoose.Promise = Promise
Mongoose.connect( appRoot.require('./dist/lib/config').database.connection )
const accountLib = appRoot.require('./dist/lib/account')
const models = require( '../models' )( Mongoose )
models.Asset.getAsset( '56d0819b655baf4a4a7f9cad' )
.then( assetDoc => {
var jqDoc = JSON.parse(JSON.stringify(assetDoc.toJSON()))
// Show the CURRENT description
console.log('[IN QUERY - Before Modify]\n\t', jqDoc.attributes[1].value)
assetDoc.attr('Description').set( 'Date-'+Date.now() )
return assetDoc.save()
} )
.then(data => {
// Just show the Description AFTER it was saved
console.log('[AFTER QUERY - AFTER Modify]\n\t', data.attributes[1].value)
})
.catch( err => console.error( 'ERROR:',err ) )
.finally( () => {
Mongoose.connection.close()
console.log('# Connection Closed')
})
[IN QUERY - Before Modify]
Date-1474915946697
[MIDDLEWARE ORIGINAL Desc]
Date-1474916372134
[MIDDLEWARE NEW Desc]
Date-1474916372134
[AFTER QUERY - AFTER Modify]
Date-1474916372134
# Connection Closed
Share
Improve this question
edited Sep 26, 2016 at 19:02
Justin
asked Sep 23, 2016 at 7:12
JustinJustin
2,0496 gold badges23 silver badges41 bronze badges
3
|
6 Answers
Reset to default 3Ok, The first part of your question is correctly answered by Avraam Mavridis so I will only focus on your last update in the question.
pre.save
actually doesn't hold the actual document that currently exixts in the database, instead it is the document which is going to be saved, and contains the changes done to the document, i.e. the updated document.
post.save
holds the real document that is stored in the database, hence still the updated version. So you can not see the change that is done just looking at this
in both pre
and post
save
.
Now if you want to see the real values that existed in the database you need to fetch it from database before it is changed and saved, i.e. in pre.save
.
One way you could do this is simply query the document from database
var originalDesc
module.exports = ( schema, options ) => {
schema.pre( 'save', function( next ) {
Asset.getAsset( '56d0819b655baf4a4a7f9cad' )
.then( assetDoc => {
originalDesc = assetDoc.attributes[1].value;
console.log( '[MIDDLEWARE ORIGINAL Desc]\n\t', originalDesc )
next()
} );
} );
schema.post( 'save', function( ) {
var newDesc = this.toJSON().attributes[1].value
console.log( '[MIDDLEWARE NEW Desc]\n\t', newDesc)
} )
}
There is an alternative way than this using custom setter, and there is already a good answer here, but this would require to set a custom setter for each property
schema.path('name').set(function (newVal) {
this.originalDesc = this.Description;
});
schema.pre('save', function (next) {
console.log( '[MIDDLEWARE ORIGINAL Desc]\n\t', this.originalDesc )
next();
})
schema.post( 'save', function( ) {
var newDesc = this.toJSON().attributes[1].value
console.log( '[MIDDLEWARE NEW Desc]\n\t', newDesc)
} )
Hope this helps.
The origDocument
has reference to the this.toJSON()
and the moment you call the console.log
the value of the actual object where the reference points has already change. Use something like JSON.stringify
to compare the values.
origDocument = JSON.stringify( this.toJSON() )
I think you are misunderstanding how the pre/post hooks work in mongoose. When you grab the document (As you are doing) and resaving it. It will not have whatever variable was originally in the document. It will have whatever is currently in the document.
So, you are doing this:
- Grab Document (67)
- Modify Document < (You did the +1 here) (68 now)
- Document.Save() called
- Pre-save printing out the current document (68)
- Post-save printing out the current document (68)
I think what you want to do is implement an instance method on your schema which you can use to define the logic you want. You would call this before you call .save() (Or use it to just call .save() after you have performed your own logic)
example:
schema.methods.createRevisionHistory= function(object, callback) {
// Do comparison logic between this. and object.
// modify document (this) accordingly
// this.save(function(err, doc) {
// if(err)
// return callback(err)
// callback(doc);
// })
};
Hope this helps
Read More: http://mongoosejs.com/docs/guide.html#methods
origDocument have the reference of this.toJSON so when this.toJSON changed in post('save') origDocument also get changed. Try below code:
var origDocument
var testVar = 'Goodbye World'
module.exports = ( schema, options ) => {
schema.pre( 'save', function( next ) {
// Store the original value of the documents attrCache.Description value
origDocument = JSON.parse(JSON.strinigify(this.toJSON().attrCache.Description))
// Change the testVar value to see if the change is reflected in post(save)
testVar = 'Hello World'
next()
} )
schema.post( 'save', function( ) {
// Attempt to compare the documents previous value of attrCache.Description, with the new value
console.log("BEFORE:", origDocument)
console.log("AFTER:", this.toJSON().attrCache.Description)
// Both of the above values are the same! >.<
console.log('post(save):',testVar) // result: post(save):Hello World
// But the above works just fine..
} )
}
Using JSON.parse(JSON.stringify()) I have cleared the reference.
Hope this helps!!!
You can use yet another middleware, and temporarily set the current value to a non-defined attribute (so it won't be saved to DB upon save
call).
E.g.
schema.post('init', function(doc) {
// when document is loaded we store copy of it to separate variable
// which will be later used for tracking changes
this._original = doc.toJSON({depopulate: true});
});
And then in post save hook do the comparison:
schema.post('save', function(doc) {
// do the diffing of revisions here
});
In our API, I solved this by using document.$locals
as the storage location for the original values. document.$locals
does not get passed to the database and it gets persisted between middleware calls.
In post('find')
and post('findOne')
hooks:
doc.$locals.originalValues = doc.toObject();
In pre('save')
and post('save')
hooks:
let changes = doc.getChanges()
, originalValues = doc.$locals.originalValues;
if (changes.$set) {
for (let key in changes.$set) {
_.set(result, key, originalValues[key]);
result[key] = originalValues[key]; // May be undefined
}
}
if (changes.$unset) {
for (let key in changes.$unset) {
_.set(result, key, originalValues[key]);
result[key] = originalValues[key]; // Should be defined
}
}
Those are the relevant parts of the code. There's plenty of error checking and edge case detection as well, but fundamentally we're storing the original document whenever we retrieve it, so those values can be compared to the data being saved.
console.log( '[MIDDLEWARE ORIGINAL Desc]\n\t', this. attributes )
andconsole.log( '[MIDDLEWARE NEW Desc]\n\t', this.attributes)
show? – malix Commented Sep 27, 2016 at 17:25