Instance-level events #2090

Open
Neamar opened this Issue May 19, 2014 · 6 comments

Projects

None yet

3 participants

@Neamar
Neamar commented May 19, 2014

Mongoose model inherits from Event Emitter, which lets us do stuff like this:

MyModel.on('init', function() {})

Pretty cool.

However, this event is broadcasted to all instances, even when sent from a custom instance. Meaning this code will result in really weird behavior:

var MyModel = require('mongoose').model('Document');

var myModel = new MyModel();
var myModel2 = new MyModel();

myModel.on('event', function() {
  console.log("Event on myModel");
});

myModel2.emit('event');

This code will display "Event on myModel", although it was dispatched on myModel2.
(this is even weirder in mongoosastic plugin, which globally dispatch 'es-indexed' event for each document)

Is there any way to have "instance-level-event-emitter" ? Custom middleware could do the trick too, although i found no information regarding this.

@rricard
rricard commented May 19, 2014

I was stuck with this issue for quite some time. I just didn't figured out it came from mongoose and not mongoosastic. Now I found an alternative but it could be cool to have it fixed 👍

@vkarpov15 vkarpov15 added the 4.x label May 19, 2014
@Neamar
Neamar commented May 26, 2014

Okay, so after reading http://opensourcejason.info/2013/10/08/properly-extending-eventemitter-in-node-js/ I made my own custom onInstance method function, which is "on for this instance only".

var util = require('util');


schema.methods.onInstance = function(event, func) {
  if(this._events === Object.getPrototypeOf(this)._events) {
    // Clone _events to dissociate this instance events from the model events
    var newEvents = {};
    for(var key in this._events) {
      var val = this._events[key];
      newEvents[key] = util.isArray(val) ? val.slice() : val;
    }
    this._events = newEvents;
  }

  // Register the event on a clean _events stack
  this.on(event, func);
};

This let me keep the existing Mongoose code without breaking anything, while at the same time allowing me to register some callbacks for one instance only.

Note however that functions registered with on() directly on the Model after a call to onInstance() will not be called, as the instance is not tied anymore to the main model and only use a copy of the listener stack after the first call to onInstance().

@vkarpov15 vkarpov15 removed the 4.x label Feb 10, 2015
@Neamar
Neamar commented Jul 23, 2015

Hey @vkarpov15, you removed the 4.x label but the new implementation does not allow for the quick fix listed above.

Any idea (or plan) on how to implement this using the new method? It looks to be more tricky...

@vkarpov15
Collaborator

I think this should be pretty easily implementable via plugin. Here's some quick pseudo-code. Be wary of bugs, I haven't proven it correct nor tested it :)

function plugin(schema) {
  schema.queue('createEmitter', []);

  schema.methods.createEmitter = function() {
    this.$__emitter = new EventEmitter();
  };

  ['on', 'once', 'emit'].forEach(function(fn) {
    schema.methods[fn] = function() {
      this.$__emitter[fn].apply(this.$__emitter, arguments);
    };
  });
}
@vkarpov15 vkarpov15 added the plugin label Jul 23, 2015
@Neamar
Neamar commented Jul 24, 2015

Indeed, however doing so would also "cut" the instance from "model level" events.

Document.on("modelLevel", function(){});

// where document implements the above plugin
var document = new Document();
document.createEmitter();
document.on("instanceLevel", function() {});

// This would not work anymore, since we're using a custom eventEmitter for each instance
document.emit("modelLevel");
@vkarpov15
Collaborator

With the above plugin you can still listen to model-level events with Document.constructor.on().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment