最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Sharing data between Mongoose middleware methods pre save and post save - Stack Overflow

programmeradmin0浏览0评论

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
  • I seriously cant get why it in the first console output (first line of last snippet), but un the function of the post middleware hook, it doesnt – Justin Commented Sep 26, 2016 at 17:37
  • what do console.log( '[MIDDLEWARE ORIGINAL Desc]\n\t', this. attributes ) and console.log( '[MIDDLEWARE NEW Desc]\n\t', this.attributes) show? – malix Commented Sep 27, 2016 at 17:25
  • @Justin Do you actually want both versions of the data, or do you want to know if a certain property has been changed? – Omar A Commented Oct 3, 2016 at 16:40
Add a comment  | 

6 Answers 6

Reset to default 3

Ok, 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:

  1. Grab Document (67)
  2. Modify Document < (You did the +1 here) (68 now)
  3. Document.Save() called
  4. Pre-save printing out the current document (68)
  5. 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.

发布评论

评论列表(0)

  1. 暂无评论