Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sharing data between Mongoose middleware methods pre save and post save

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
like image 344
Justin Avatar asked Sep 23 '16 07:09

Justin


People also ask

What is pre-save in Mongoose?

You can define your custom middleware functions on the schema level itself using these hooks. Here the classical example would be the use of pre-save hook to encrypt the password while the document is being saved or if the password has changed during an update operation.

What is difference between create and save in Mongoose?

save() is considered to be an instance method of the model, while the . create() is called straight from the Model as a method call, being static in nature, and takes the object as a first parameter.

What does save () do in Mongoose?

Mongoose | save() Function The save() function is used to save the document to the database. Using this function, new documents can be added to the database.

What is pre-save hook?

It might be obvious, but a pre-save hook is middleware that is executed when a document is saved.


1 Answers

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.

like image 165
Naeem Shaikh Avatar answered Sep 26 '22 17:09

Naeem Shaikh