From bef1bc1ca4fb5fc16be5a2135f2b01526b591f32 Mon Sep 17 00:00:00 2001 From: Ritchie Martori Date: Tue, 28 Jan 2014 13:45:00 -0800 Subject: [PATCH 1/2] Add change / delete events --- lib/dao.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/dao.js b/lib/dao.js index b556e7b1..954f3052 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -126,6 +126,7 @@ DataAccessObject.create = function (data, callback) { function modelCreated() { if (--wait === 0) { callback(gotError ? errors : null, instances); + if(!gotError) instances.forEach(Model.emit.bind('changed')); } } } @@ -168,6 +169,7 @@ DataAccessObject.create = function (data, callback) { saveDone.call(obj, function () { createDone.call(obj, function () { callback(err, obj); + if(!err) Model.emit('changed', obj); }); }); }, obj); @@ -607,6 +609,7 @@ DataAccessObject.remove = DataAccessObject.deleteAll = DataAccessObject.destroyAll = function destroyAll(where, cb) { if (stillConnecting(this.getDataSource(), this, arguments)) return; + var Model = this; if (!cb && 'function' === typeof where) { cb = where; @@ -615,6 +618,7 @@ DataAccessObject.remove = if (!where) { this.getDataSource().connector.destroyAll(this.modelName, function (err, data) { cb && cb(err, data); + if(!err) Model.emit('deletedAll'); }.bind(this)); } else { // Support an optional where object @@ -622,6 +626,7 @@ DataAccessObject.remove = where = this._coerce(where); this.getDataSource().connector.destroyAll(this.modelName, where, function (err, data) { cb && cb(err, data); + if(!err) Model.emit('deletedAll', where); }.bind(this)); } }; @@ -635,11 +640,13 @@ DataAccessObject.removeById = DataAccessObject.deleteById = DataAccessObject.destroyById = function deleteById(id, cb) { if (stillConnecting(this.getDataSource(), this, arguments)) return; + var Model = this; this.getDataSource().connector.destroy(this.modelName, id, function (err) { if ('function' === typeof cb) { cb(err); } + if(!err) Model.emit('deleted', id); }.bind(this)); }; @@ -684,6 +691,7 @@ setRemoting(DataAccessObject.count, { */ DataAccessObject.prototype.save = function (options, callback) { if (stillConnecting(this.getDataSource(), this, arguments)) return; + var Model = this.constructor; if (typeof options == 'function') { callback = options; @@ -740,6 +748,9 @@ DataAccessObject.prototype.save = function (options, callback) { updateDone.call(inst, function () { saveDone.call(inst, function () { callback(err, inst); + if(!err) { + Model.emit('changed', inst); + } }); }); }); @@ -811,7 +822,8 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, cb if (stillConnecting(this.getDataSource(), this, arguments)) return; var inst = this; - var model = this.constructor.modelName; + var Model = this.constructor + var model = Model.modelName; if (typeof data === 'function') { cb = data; @@ -851,6 +863,7 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, cb done.call(inst, function () { saveDone.call(inst, function () { cb(err, inst); + if(!err) Model.emit('changed', inst); }); }); }); From e9097494559ecc13484b1d087dab0e22af299178 Mon Sep 17 00:00:00 2001 From: Ritchie Martori Date: Wed, 29 Jan 2014 11:03:04 -0800 Subject: [PATCH 2/2] Add tests for change / delete events --- lib/dao.js | 7 ++-- test/events.js | 81 ++++++++++++++++++++++++++++++++++++++++++++++ test/hooks.test.js | 2 ++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 test/events.js diff --git a/lib/dao.js b/lib/dao.js index 954f3052..d63dedbd 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -780,15 +780,18 @@ DataAccessObject.prototype.remove = DataAccessObject.prototype.delete = DataAccessObject.prototype.destroy = function (cb) { if (stillConnecting(this.getDataSource(), this, arguments)) return; + var Model = this.constructor; + var id = getIdValue(this.constructor, this); this.trigger('destroy', function (destroyed) { - this._adapter().destroy(this.constructor.modelName, getIdValue(this.constructor, this), function (err) { + this._adapter().destroy(this.constructor.modelName, id, function (err) { if (err) { return cb(err); } destroyed(function () { if (cb) cb(); + Model.emit('deleted', id); }); }.bind(this)); }); @@ -862,7 +865,7 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, cb } done.call(inst, function () { saveDone.call(inst, function () { - cb(err, inst); + if(cb) cb(err, inst); if(!err) Model.emit('changed', inst); }); }); diff --git a/test/events.js b/test/events.js new file mode 100644 index 00000000..b2867591 --- /dev/null +++ b/test/events.js @@ -0,0 +1,81 @@ +var should = require('./init.js'); + +describe('events', function() { + beforeEach(function(done) { + var test = this; + this.db = getSchema(); + this.TestModel = this.db.define('TestModel'); + this.db.automigrate(function(err) { + if(err) return done(err); + test.TestModel.create(function(err, inst) { + if(err) return done(err); + test.inst = inst; + done(); + }); + }); + this.shouldEmitEvent = function(eventName, listener, done) { + var timeout = setTimeout(function() { + done(new Error('did not emit ' + eventName)); + }, 100); + this.TestModel.on(eventName, function() { + clearTimeout(timeout); + listener.apply(this, arguments); + done(); + }); + } + }); + + describe('changed', function() { + it('should be emitted after save', function(done) { + var model = new this.TestModel({name: 'foobar'}); + this.shouldEmitEvent('changed', assertValidChangedArgs, done); + model.save(); + }); + it('should be emitted after upsert', function(done) { + this.shouldEmitEvent('changed', assertValidChangedArgs, done); + this.TestModel.upsert({name: 'batbaz'}); + }); + it('should be emitted after create', function(done) { + this.shouldEmitEvent('changed', assertValidChangedArgs, done); + this.TestModel.create({name: '...'}); + }); + it('should be emitted after updateAttributes', function(done) { + var test = this; + this.TestModel.create({name: 'bazzy'}, function(err, model) { + // prevent getting the changed event from "create" + process.nextTick(function() { + test.shouldEmitEvent('changed', assertValidChangedArgs, done); + model.updateAttributes({name: 'foo'}); + }); + }); + }); + }); + + describe('deleted', function() { + it('should be emitted after destroy', function(done) { + this.shouldEmitEvent('deleted', assertValidDeletedArgs, done); + this.inst.destroy(); + }); + it('should be emitted after deleteById', function(done) { + this.shouldEmitEvent('deleted', assertValidDeletedArgs, done); + this.TestModel.deleteById(this.inst.id); + }); + }); + + describe('deletedAll', function() { + it('should be emitted after destroyAll', function(done) { + this.shouldEmitEvent('deletedAll', function(where) { + where.name.should.equal('foo'); + }, done); + this.TestModel.destroyAll({name: 'foo'}); + }); + }); +}); + +function assertValidChangedArgs(obj) { + obj.should.have.property('id'); +} + +function assertValidDeletedArgs(id) { + id.should.be.ok; +} \ No newline at end of file diff --git a/test/hooks.test.js b/test/hooks.test.js index 2610f1b3..f613122b 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -402,6 +402,8 @@ describe('hooks', function () { }); }); + + function addHooks(name, done) { var called = false, random = String(Math.floor(Math.random() * 1000)); User['before' + name] = function (next, data) {