diff --git a/lib/dao.js b/lib/dao.js index e05ae73b..0862c085 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -216,6 +216,7 @@ DataAccessObject.create = function (data, options, cb) { var enforced = {}; var obj; var idValue = getIdValue(this, data); + var hookState = {}; // if we come from save if (data instanceof Model && !idValue) { @@ -230,7 +231,9 @@ DataAccessObject.create = function (data, options, cb) { Model = this.lookupModel(data); // data-specific if (Model !== obj.constructor) obj = new Model(data); - Model.notifyObserversOf('before save', { Model: Model, instance: obj }, function(err) { + Model.notifyObserversOf('before save', { + Model: Model, instance: obj, hookState: hookState + }, function(err) { if (err) return cb(err); data = obj.toObject(true); @@ -268,7 +271,9 @@ DataAccessObject.create = function (data, options, cb) { if (err) { return cb(err, obj); } - Model.notifyObserversOf('after save', { Model: Model, instance: obj }, function(err) { + Model.notifyObserversOf('after save', { + Model: Model, instance: obj, hookState: hookState + }, function(err) { cb(err, obj); if(!err) Model.emit('changed', obj); }); @@ -345,20 +350,25 @@ DataAccessObject.updateOrCreate = DataAccessObject.upsert = function upsert(data var self = this; var Model = this; + var hookState = {}; var id = getIdValue(this, data); if (!id) { return this.create(data, options, cb); } - Model.notifyObserversOf('access', { Model: Model, query: byIdQuery(Model, id) }, doUpdateOrCreate); + Model.notifyObserversOf('access', { + Model: Model, query: byIdQuery(Model, id), hookState: hookState + }, doUpdateOrCreate); function doUpdateOrCreate(err, ctx) { if (err) return cb(err); var isOriginalQuery = isWhereByGivenId(Model, ctx.query.where, id) if (Model.getDataSource().connector.updateOrCreate && isOriginalQuery) { - var context = { Model: Model, where: ctx.query.where, data: data }; + var context = { + Model: Model, where: ctx.query.where, data: data, hookState: hookState + }; Model.notifyObserversOf('before save', context, function(err, ctx) { if (err) return cb(err); @@ -394,7 +404,9 @@ DataAccessObject.updateOrCreate = DataAccessObject.upsert = function upsert(data Model.emit('changed', inst); } } else { - Model.notifyObserversOf('after save', { Model: Model, instance: obj }, function(err) { + Model.notifyObserversOf('after save', { + Model: Model, instance: obj, hookState: hookState + }, function(err) { cb(err, obj); if(!err) { Model.emit('changed', inst); @@ -478,6 +490,7 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb) var Model = this; var self = this; + var hookState = {}; function _findOrCreate(query, data) { var modelName = self.modelName; @@ -493,8 +506,9 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb) } if (created) { - Model.notifyObserversOf('after save', { Model: Model, instance: obj }, - function(err) { + Model.notifyObserversOf('after save', { + Model: Model, instance: obj, hookState: hookState + }, function(err) { if (cb.promise) { cb(err, [obj, created]); } else { @@ -526,8 +540,9 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb) this.applyScope(query); - Model.notifyObserversOf('access', { Model: Model, query: query }, - function (err, ctx) { + Model.notifyObserversOf('access', { + Model: Model, query: query, hookState: hookState + }, function (err, ctx) { if (err) return cb(err); var query = ctx.query; @@ -539,8 +554,9 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb) Model.applyProperties(enforced, obj); obj.setAttributes(enforced); - Model.notifyObserversOf('before save', { Model: Model, instance: obj }, - function(err, ctx) { + Model.notifyObserversOf('before save', { + Model: Model, instance: obj, hookState: hookState + }, function(err, ctx) { if (err) return cb(err); var obj = ctx.instance; @@ -1081,6 +1097,7 @@ DataAccessObject.find = function find(query, options, cb) { assert(typeof cb === 'function', 'The cb argument must be a function'); var self = this; + var hookState = {}; try { this._normalize(query); @@ -1105,7 +1122,9 @@ DataAccessObject.find = function find(query, options, cb) { // using all documents // TODO [fabien] use default scope here? - self.notifyObserversOf('access', { Model: self, query: query }, function(err, ctx) { + self.notifyObserversOf('access', { + Model: self, query: query, hookState: hookState + }, function(err, ctx) { if (err) return cb(err); self.getDataSource().connector.all(self.modelName, {}, function (err, data) { @@ -1199,7 +1218,9 @@ DataAccessObject.find = function find(query, options, cb) { if (options.notify === false) { self.getDataSource().connector.all(self.modelName, query, allCb); } else { - this.notifyObserversOf('access', { Model: this, query: query }, function(err, ctx) { + this.notifyObserversOf('access', { + Model: this, query: query, hookState: hookState + }, function(err, ctx) { if (err) return cb(err); var query = ctx.query; self.getDataSource().connector.all(self.modelName, query, allCb); @@ -1296,16 +1317,22 @@ DataAccessObject.remove = DataAccessObject.deleteAll = DataAccessObject.destroyA this.applyScope(query); where = query.where; - var context = { Model: Model, where: whereIsEmpty(where) ? {} : where }; + var hookState = {}; + var context = { + Model: Model, where: whereIsEmpty(where) ? {} : where, hookState: hookState + }; + if (options.notify === false) { doDelete(where); } else { query = { where: whereIsEmpty(where) ? {} : where }; - Model.notifyObserversOf('access', - { Model: Model, query: query }, - function(err, ctx) { + Model.notifyObserversOf('access', { + Model: Model, query: query, hookState: hookState + }, function(err, ctx) { if (err) return cb(err); - var context = { Model: Model, where: ctx.query.where }; + var context = { + Model: Model, where: ctx.query.where, hookState: hookState + }; Model.notifyObserversOf('before delete', context, function(err, ctx) { if (err) return cb(err); doDelete(ctx.where); @@ -1338,7 +1365,9 @@ DataAccessObject.remove = DataAccessObject.deleteAll = DataAccessObject.destroyA return cb(err, data); } - Model.notifyObserversOf('after delete', { Model: Model, where: where }, function(err) { + Model.notifyObserversOf('after delete', { + Model: Model, where: where, hookState: hookState + }, function(err) { cb(err, data); if (!err) Model.emit('deletedAll', whereIsEmpty(where) ? undefined : where); @@ -1459,7 +1488,11 @@ DataAccessObject.count = function (where, options, cb) { } var Model = this; - this.notifyObserversOf('access', { Model: Model, query: { where: where } }, function(err, ctx) { + var hookState = {}; + + this.notifyObserversOf('access', { + Model: Model, query: { where: where }, hookState: hookState + }, function(err, ctx) { if (err) return cb(err); where = ctx.query.where; Model.getDataSource().connector.count(Model.modelName, cb, where); @@ -1506,8 +1539,11 @@ DataAccessObject.prototype.save = function (options, cb) { var inst = this; var modelName = Model.modelName; + var hookState = {}; - Model.notifyObserversOf('before save', { Model: Model, instance: inst }, function(err) { + Model.notifyObserversOf('before save', { + Model: Model, instance: inst, hookState: hookState + }, function(err) { if (err) return cb(err); var data = inst.toObject(true); @@ -1542,7 +1578,9 @@ DataAccessObject.prototype.save = function (options, cb) { return cb(err, inst); } inst._initProperties(data, { persisted: true }); - Model.notifyObserversOf('after save', { Model: Model, instance: inst }, function(err) { + Model.notifyObserversOf('after save', { + Model: Model, instance: inst, hookState: hookState + }, function(err) { if (err) return cb(err, inst); updateDone.call(inst, function () { saveDone.call(inst, function () { @@ -1625,15 +1663,19 @@ DataAccessObject.updateAll = function (where, data, options, cb) { where = query.where; var Model = this; + var hookState = {}; - Model.notifyObserversOf('access', { Model: Model, query: { where: where } }, function(err, ctx) { + Model.notifyObserversOf('access', { + Model: Model, query: { where: where }, hookState: hookState + }, function(err, ctx) { if (err) return cb(err); Model.notifyObserversOf( 'before save', { Model: Model, where: ctx.query.where, - data: data + data: data, + hookState: hookState }, function(err, ctx) { if (err) return cb(err); @@ -1662,7 +1704,8 @@ DataAccessObject.updateAll = function (where, data, options, cb) { { Model: Model, where: where, - data: data + data: data, + hookState: hookState }, function(err, ctx) { return cb(err, count); @@ -1714,16 +1757,15 @@ DataAccessObject.prototype.remove = var self = this; var Model = this.constructor; var id = getIdValue(this.constructor, this); + var hookState = {}; - Model.notifyObserversOf( - 'access', - { Model: Model, query: byIdQuery(Model, id) }, - function(err, ctx) { + Model.notifyObserversOf('access', { + Model: Model, query: byIdQuery(Model, id), hookState: hookState + }, function(err, ctx) { if (err) return cb(err); - Model.notifyObserversOf( - 'before delete', - { Model: Model, where: ctx.query.where }, - function(err, ctx) { + Model.notifyObserversOf('before delete',{ + Model: Model, where: ctx.query.where, hookState: hookState + }, function(err, ctx) { if (err) return cb(err); doDeleteInstance(ctx.where); }); @@ -1736,7 +1778,9 @@ DataAccessObject.prototype.remove = // We must switch to full query-based delete. Model.deleteAll(where, { notify: false }, function(err) { if (err) return cb(err); - Model.notifyObserversOf('after delete', { Model: Model, where: where }, function(err) { + Model.notifyObserversOf('after delete', { + Model: Model, where: where, hookState: hookState + }, function(err) { cb(err); if (!err) Model.emit('deleted', id); }); @@ -1751,7 +1795,9 @@ DataAccessObject.prototype.remove = } destroyed(function () { - Model.notifyObserversOf('after delete', { Model: Model, where: where }, function(err) { + Model.notifyObserversOf('after delete', { + Model: Model, where: where, hookState: hookState + }, function(err) { cb(err); if (!err) Model.emit('deleted', id); }); @@ -1858,7 +1904,8 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, op var inst = this; var Model = this.constructor; var model = Model.modelName; - + var hookState = {}; + // Convert the data to be plain object so that update won't be confused if (data instanceof Model) { data = data.toObject(false); @@ -1868,7 +1915,8 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, op var context = { Model: Model, where: byIdQuery(Model, getIdValue(Model, inst)).where, - data: data + data: data, + hookState: hookState }; Model.notifyObserversOf('before save', context, function(err, ctx) { @@ -1905,7 +1953,9 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, op done.call(inst, function () { saveDone.call(inst, function () { if (err) return cb(err, inst); - Model.notifyObserversOf('after save', { Model: Model, instance: inst }, function(err) { + Model.notifyObserversOf('after save', { + Model: Model, instance: inst, hookState: hookState + }, function(err) { if(!err) Model.emit('changed', inst); cb(err, inst); }); diff --git a/test/persistence-hooks.suite.js b/test/persistence-hooks.suite.js index 0a44e567..563ce245 100644 --- a/test/persistence-hooks.suite.js +++ b/test/persistence-hooks.suite.js @@ -1095,6 +1095,31 @@ module.exports = function(dataSource, should) { }); }); + it('applies propagates hookState from `before delete` to `after delete` hook', function(done) { + TestModel.observe('before delete', pushContextAndNext(function(ctx) { + ctx.hookState.foo = 'bar'; + })); + + TestModel.observe('after delete', pushContextAndNext(function(ctx) { + ctx.hookState.foo = ctx.hookState.foo.toUpperCase(); + })); + + existingInstance.delete(function(err) { + if (err) return done(err); + observedContexts.should.eql([ + aTestModelCtx({ + hookState: { foo: 'bar', test: true }, + where: { id: '1' } + }), + aTestModelCtx({ + hookState: { foo: 'BAR', test: true }, + where: { id: '1' } + }) + ]); + done(); + }); + }); + it('triggers hooks only once', function(done) { TestModel.observe('access', pushNameAndNext('access')); TestModel.observe('after delete', pushNameAndNext('after delete')); @@ -1202,9 +1227,14 @@ module.exports = function(dataSource, should) { }); }); - function pushContextAndNext() { + function pushContextAndNext(fn) { return function(context, next) { + if (typeof fn === 'function') { + fn(context); + } + context = deepCloneToObject(context); + context.hookState.test = true; if (typeof observedContexts === 'string') { observedContexts = context; @@ -1246,6 +1276,9 @@ module.exports = function(dataSource, should) { function aTestModelCtx(ctx) { ctx.Model = TestModel; + if (!ctx.hookState) { + ctx.hookState = { test: true }; + } return deepCloneToObject(ctx); }