Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Post init event does not fire after document creation #2363

Closed
ioncreature opened this issue Oct 10, 2014 · 15 comments
Closed

Post init event does not fire after document creation #2363

ioncreature opened this issue Oct 10, 2014 · 15 comments

Comments

@ioncreature
Copy link

Here is CardType.js

var CardType = new Schema({
    name: {type: String, required: true},
    //...
});

CardType.post( 'init', function(){
    this._original = this.toObject();
});

I expect that all documents will have _original field

CardType.create( [{name: 'test'}, {name: 'test2'}], function( error ){
    var cardTypes = Array.prototype.splice.call( arguments, 1 );

    cardTypes.forEach( function( doc ){
        console.log( doc._original );
    });
});

doc._original always return undefined

Is it expected behavior or bug?

@vkarpov15 vkarpov15 added the bug? label Oct 11, 2014
@vkarpov15
Copy link
Collaborator

Not sure, I'll investigate

@ioncreature
Copy link
Author

I use version 3.8.17

@vkarpov15
Copy link
Collaborator

Hmm have you tried using the doc parameter?

CardType.post('init', function(doc) {
    doc._original = doc.toObject();
});

I'm not sure if this is the actual document in a post-save hook.

@ioncreature
Copy link
Author

Да, Валерий.

I've tried to use that you said. But nothing happens.

// CardType.js
CardType.post( 'init', function( doc ){
    doc._original = doc.toObject();
    console.log( 'post init', doc.name );
});

There was no console output. So in my case post init handler did not fire at all.

@vkarpov15
Copy link
Collaborator

Hmm perhaps an exception is happening? Try putting the doc._original and console.log lines in a try/catch. I'm just confused because post('init') has test coverage, so this in theory shouldn't be happening.

Also, you might want to try upgrading to 3.8.18 - there was a minor bug related to .save() in 3.8.17 and create() calls save() under the hood

@ioncreature
Copy link
Author

Result is the same, there is no changes in 3.8.18

Ok. I will show you all the real code:

// plugins/state.js
module.exports = function( schema ){
    schema.post( 'init', function( doc ){
        doc._original = doc.toObject();
        doc._isNew = doc.isNew;
    });

    schema.post( 'remove', function(){
        this._isRemoved = true;
    });
};
// CardType.js
var Schema = require( 'mongoose' ).Schema,
    util = require( '../util' ),
    statePlugin = require( './plugins/state' ),
    CardType = new Schema({
        name: {type: String, required: true},
        issuerId: {type: Schema.Types.ObjectId, ref: 'Issuer', index: true, required: true},
        magneticStripe: {type: Boolean, default: false},
        cardNumber: {type: Boolean, default: false},
        cardNumberLength: {type: Number, default: 0},
        userName: {type: Boolean, default: false},
        chip: {type: Boolean, default: false},
        nfc: {type: Boolean, default: false}
    });

CardType.plugin( statePlugin );

CardType.post( 'save', function( doc ){
    if ( doc._original && doc._original.name !== doc.name )
        this.model( 'Card' ).update( {typeId: doc._id}, {typeName: doc.name}, {multi: true}, util.noop );
});

module.exports = CardType;
// issuer.js
async.series({
    update: function( cb ){
        util.mixin( issuer, issuerData );
        issuer.save( cb );
    },
    cardTypes: function( cb ){
        CardType.create( cardTypes, cb );
    }
}, function( error, result ){
    // ...
});

But post init handler works fine when I write just:

var type = new CardType;
type.name = 'test';
type.issuerId = ObjectId.createFromTime( Date.now() );
type.save( handler );

Thanks.

@vkarpov15
Copy link
Collaborator

Turns out this is unfortunately expected behavior right now - init event is only emitted when a document is returned from the db, not when a document is created using new Model(). Somewhat wonky, I agree this should be changed.

@vkarpov15 vkarpov15 added enhancement This issue is a user-facing general improvement that doesn't fix a bug or add a new feature and removed bug? labels Oct 31, 2014
@bshamblen
Copy link

Similar to the code in the original issue above I was storing a copy of the existing document in the post init hook, then in the pre save hook I was comparing the original document with the updated doc (if not new), then in the post save hook I had logic to send a notification of the changes.

As @ioncreature mentioned in his original post, this all used to work. Once I upgraded to 4.0 I received the same error message Object #<EventEmitter> has no method 'toObject'. I've been trying to come up with a workable solution, but I can't see a way to persist the original document through to the post save event.

My original code

schema.post('init', function() {
    this._original = this.toObject();
    this._changes = []; //array of objects with the path, new and old values. Populated by `observe` function in pre `save` hook
});

schema.pre('save', function(next) {
    //other logic here. Cut out for this example..

    this.observe(); //compares the current doc with the original and stores the new and old values in `this._changes` array.
    next();
});

schema.post('save', function(doc) {
    if (this._original && this._changes.length > 0) {
        //send web hook notification, with changes
    } else {
        //send web hook notification, with new document
    }

    //reset the values, in case the document is saved again.
    this._original = null;
    this._original = this;
    this._changes = [];
});

The problem with the new post init architecture is that if I now add a parameter to the hook function I can get the doc and add a copy to itself, but that copy will no longer be available in the pre or post save hooks.

Changed to

schema.post('init', function(doc) {
    doc._original = doc.toObject();
    doc._changes = [];
});

But in the pre save hook this._original is undefined, even for documents that we returned from the database. Any suggestions on how to reliably save a copy of the original document and have is survive the document lifecycle.

@vkarpov15
Copy link
Collaborator

Please open up a separate issue for that, that is an unrelated issue.

@ClaytonSmith
Copy link

Is there a workaround for this issue? I need a way to apply some foos and bars to my model's data only when the doc is first created.

@vkarpov15
Copy link
Collaborator

vkarpov15 commented Jan 15, 2016

@ClaytonSmith schema.methods.foo = function() { this.bar = 'baz'; }; schema.queue('foo'); foo gets called every time a document with that schema is created.

@vkarpov15 vkarpov15 added the discussion If you have any thoughts or comments on this issue, please share them! label Jan 15, 2016
@vkarpov15 vkarpov15 modified the milestones: 4.5, 5.0 Jan 15, 2016
@alecmev
Copy link

alecmev commented Jun 4, 2017

@vkarpov15 Please, update your last comment with a proper example of schema.queue usage, like here. This was quite misleading, considering that it can't be found in the docs.

Also, I'd like to note that it's synchronous only.

@vkarpov15
Copy link
Collaborator

@jeremejevs thanks for pointing this out, fixed 👍

@vkarpov15
Copy link
Collaborator

I'm going to close this for now, schema.queue() is the way to go.

@vkarpov15 vkarpov15 removed this from the Parking Lot milestone Feb 15, 2019
@vkarpov15 vkarpov15 removed discussion If you have any thoughts or comments on this issue, please share them! enhancement This issue is a user-facing general improvement that doesn't fix a bug or add a new feature labels Feb 15, 2019
@vkarpov15
Copy link
Collaborator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants