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,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
};

View File

@ -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();