From 8605da3ac6da385ee23849ab445a55e084e49093 Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Thu, 5 Mar 2015 18:16:12 +0100 Subject: [PATCH] Improve instance-level operation hooks "before delete" and "after delete" hooks receive `ctx.instance` when a single model is being deleted. "before save" hook receives `ctx.currentInstance` when triggered by `prototype.updateAttributes()`. Note that "after save" hook triggered by `prototype.updateAttributes()` already provides `ctx.instance`. --- lib/dao.js | 30 ++++++++++++++++++------ test/persistence-hooks.suite.js | 41 +++++++++++++++++++++++++-------- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/lib/dao.js b/lib/dao.js index bf48c003..4062d24f 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -1732,18 +1732,23 @@ DataAccessObject.prototype.remove = assert(typeof options === 'object', 'The options argument should be an object'); assert(typeof cb === 'function', 'The cb argument should be a function'); - var self = this; + var inst = this; var Model = this.constructor; var id = getIdValue(this.constructor, this); var hookState = {}; var context = { - Model: Model, query: byIdQuery(Model, id), hookState: hookState + Model: Model, query: byIdQuery(Model, id), hookState: hookState }; Model.notifyObserversOf('access', context, function(err, ctx) { if (err) return cb(err); - var context = { Model: Model, where: ctx.query.where, hookState: hookState }; + var context = { + Model: Model, + where: ctx.query.where, + instance: inst, + hookState: hookState + }; Model.notifyObserversOf('before delete', context, function(err, ctx) { if (err) return cb(err); doDeleteInstance(ctx.where); @@ -1757,7 +1762,12 @@ DataAccessObject.prototype.remove = // We must switch to full query-based delete. Model.deleteAll(where, { notify: false }, function(err) { if (err) return cb(err); - var context = { Model: Model, where: where, hookState: hookState }; + var context = { + Model: Model, + where: where, + instance: inst, + hookState: hookState + }; Model.notifyObserversOf('after delete', context, function(err) { cb(err); if (!err) Model.emit('deleted', id); @@ -1766,14 +1776,19 @@ DataAccessObject.prototype.remove = return; } - self.trigger('destroy', function (destroyed) { - self._adapter().destroy(self.constructor.modelName, id, function (err) { + inst.trigger('destroy', function (destroyed) { + inst._adapter().destroy(inst.constructor.modelName, id, function (err) { if (err) { return cb(err); } destroyed(function () { - var context = { Model: Model, where: where, hookState: hookState }; + var context = { + Model: Model, + where: where, + instance: inst, + hookState: hookState + }; Model.notifyObserversOf('after delete', context, function(err) { cb(err); if (!err) Model.emit('deleted', id); @@ -1893,6 +1908,7 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, op Model: Model, where: byIdQuery(Model, getIdValue(Model, inst)).where, data: data, + currentInstance: inst, hookState: hookState }; diff --git a/test/persistence-hooks.suite.js b/test/persistence-hooks.suite.js index da6263b7..d9e26184 100644 --- a/test/persistence-hooks.suite.js +++ b/test/persistence-hooks.suite.js @@ -535,12 +535,15 @@ module.exports = function(dataSource, should) { it('triggers `before save` hook', function(done) { TestModel.observe('before save', pushContextAndNext()); - existingInstance.name = 'changed'; + var currentInstance = deepCloneToObject(existingInstance); + existingInstance.updateAttributes({ name: 'changed' }, function(err) { if (err) return done(err); + existingInstance.name.should.equal('changed'); observedContexts.should.eql(aTestModelCtx({ where: { id: existingInstance.id }, - data: { name: 'changed' } + data: { name: 'changed' }, + currentInstance: currentInstance })); done(); }); @@ -736,10 +739,24 @@ module.exports = function(dataSource, should) { { id: existingInstance.id, name: 'updated name' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ - where: { id: existingInstance.id }, - data: { id: existingInstance.id, name: 'updated name' } - })); + if (dataSource.connector.updateOrCreate) { + // Atomic implementations of `updateOrCreate` cannot + // provide full instance as that depends on whether + // UPDATE or CREATE will be triggered + observedContexts.should.eql(aTestModelCtx({ + where: { id: existingInstance.id }, + data: { id: existingInstance.id, name: 'updated name' } + })); + } else { + // currentInstance is set, because a non-atomic `updateOrCreate` + // will use `prototype.updateAttributes` internally, which + // exposes this to the context + observedContexts.should.eql(aTestModelCtx({ + where: { id: existingInstance.id }, + data: { id: existingInstance.id, name: 'updated name' }, + currentInstance: existingInstance + })); + } done(); }); }); @@ -1026,7 +1043,8 @@ module.exports = function(dataSource, should) { existingInstance.delete(function(err) { if (err) return done(err); observedContexts.should.eql(aTestModelCtx({ - where: { id: existingInstance.id } + where: { id: existingInstance.id }, + instance: existingInstance })); done(); }); @@ -1068,7 +1086,8 @@ module.exports = function(dataSource, should) { existingInstance.delete(function(err) { if (err) return done(err); observedContexts.should.eql(aTestModelCtx({ - where: { id: existingInstance.id } + where: { id: existingInstance.id }, + instance: existingInstance })); done(); }); @@ -1109,11 +1128,13 @@ module.exports = function(dataSource, should) { observedContexts.should.eql([ aTestModelCtx({ hookState: { foo: 'bar', test: true }, - where: { id: '1' } + where: { id: '1' }, + instance: existingInstance }), aTestModelCtx({ hookState: { foo: 'BAR', test: true }, - where: { id: '1' } + where: { id: '1' }, + instance: existingInstance }) ]); done();