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`.
This commit is contained in:
Fabien Franzen 2015-03-05 18:16:12 +01:00 committed by Miroslav Bajtoš
parent 2a24273b4f
commit 8605da3ac6
2 changed files with 54 additions and 17 deletions

View File

@ -1732,7 +1732,7 @@ DataAccessObject.prototype.remove =
assert(typeof options === 'object', 'The options argument should be an object'); assert(typeof options === 'object', 'The options argument should be an object');
assert(typeof cb === 'function', 'The cb argument should be a function'); assert(typeof cb === 'function', 'The cb argument should be a function');
var self = this; var inst = this;
var Model = this.constructor; var Model = this.constructor;
var id = getIdValue(this.constructor, this); var id = getIdValue(this.constructor, this);
var hookState = {}; var hookState = {};
@ -1743,7 +1743,12 @@ DataAccessObject.prototype.remove =
Model.notifyObserversOf('access', context, function(err, ctx) { Model.notifyObserversOf('access', context, function(err, ctx) {
if (err) return cb(err); 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) { Model.notifyObserversOf('before delete', context, function(err, ctx) {
if (err) return cb(err); if (err) return cb(err);
doDeleteInstance(ctx.where); doDeleteInstance(ctx.where);
@ -1757,7 +1762,12 @@ DataAccessObject.prototype.remove =
// We must switch to full query-based delete. // We must switch to full query-based delete.
Model.deleteAll(where, { notify: false }, function(err) { Model.deleteAll(where, { notify: false }, function(err) {
if (err) return cb(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) { Model.notifyObserversOf('after delete', context, function(err) {
cb(err); cb(err);
if (!err) Model.emit('deleted', id); if (!err) Model.emit('deleted', id);
@ -1766,14 +1776,19 @@ DataAccessObject.prototype.remove =
return; return;
} }
self.trigger('destroy', function (destroyed) { inst.trigger('destroy', function (destroyed) {
self._adapter().destroy(self.constructor.modelName, id, function (err) { inst._adapter().destroy(inst.constructor.modelName, id, function (err) {
if (err) { if (err) {
return cb(err); return cb(err);
} }
destroyed(function () { 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) { Model.notifyObserversOf('after delete', context, function(err) {
cb(err); cb(err);
if (!err) Model.emit('deleted', id); if (!err) Model.emit('deleted', id);
@ -1893,6 +1908,7 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, op
Model: Model, Model: Model,
where: byIdQuery(Model, getIdValue(Model, inst)).where, where: byIdQuery(Model, getIdValue(Model, inst)).where,
data: data, data: data,
currentInstance: inst,
hookState: hookState hookState: hookState
}; };

View File

@ -535,12 +535,15 @@ module.exports = function(dataSource, should) {
it('triggers `before save` hook', function(done) { it('triggers `before save` hook', function(done) {
TestModel.observe('before save', pushContextAndNext()); TestModel.observe('before save', pushContextAndNext());
existingInstance.name = 'changed'; var currentInstance = deepCloneToObject(existingInstance);
existingInstance.updateAttributes({ name: 'changed' }, function(err) { existingInstance.updateAttributes({ name: 'changed' }, function(err) {
if (err) return done(err); if (err) return done(err);
existingInstance.name.should.equal('changed');
observedContexts.should.eql(aTestModelCtx({ observedContexts.should.eql(aTestModelCtx({
where: { id: existingInstance.id }, where: { id: existingInstance.id },
data: { name: 'changed' } data: { name: 'changed' },
currentInstance: currentInstance
})); }));
done(); done();
}); });
@ -736,10 +739,24 @@ module.exports = function(dataSource, should) {
{ id: existingInstance.id, name: 'updated name' }, { id: existingInstance.id, name: 'updated name' },
function(err, instance) { function(err, instance) {
if (err) return done(err); if (err) return done(err);
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({ observedContexts.should.eql(aTestModelCtx({
where: { id: existingInstance.id }, where: { id: existingInstance.id },
data: { id: existingInstance.id, name: 'updated name' } 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(); done();
}); });
}); });
@ -1026,7 +1043,8 @@ module.exports = function(dataSource, should) {
existingInstance.delete(function(err) { existingInstance.delete(function(err) {
if (err) return done(err); if (err) return done(err);
observedContexts.should.eql(aTestModelCtx({ observedContexts.should.eql(aTestModelCtx({
where: { id: existingInstance.id } where: { id: existingInstance.id },
instance: existingInstance
})); }));
done(); done();
}); });
@ -1068,7 +1086,8 @@ module.exports = function(dataSource, should) {
existingInstance.delete(function(err) { existingInstance.delete(function(err) {
if (err) return done(err); if (err) return done(err);
observedContexts.should.eql(aTestModelCtx({ observedContexts.should.eql(aTestModelCtx({
where: { id: existingInstance.id } where: { id: existingInstance.id },
instance: existingInstance
})); }));
done(); done();
}); });
@ -1109,11 +1128,13 @@ module.exports = function(dataSource, should) {
observedContexts.should.eql([ observedContexts.should.eql([
aTestModelCtx({ aTestModelCtx({
hookState: { foo: 'bar', test: true }, hookState: { foo: 'bar', test: true },
where: { id: '1' } where: { id: '1' },
instance: existingInstance
}), }),
aTestModelCtx({ aTestModelCtx({
hookState: { foo: 'BAR', test: true }, hookState: { foo: 'BAR', test: true },
where: { id: '1' } where: { id: '1' },
instance: existingInstance
}) })
]); ]);
done(); done();