Implement operation hooks' context

This commit is contained in:
Fabien Franzen 2015-03-05 11:55:04 +01:00
parent b7abb08da8
commit f42859f2e5
2 changed files with 122 additions and 39 deletions

View File

@ -216,6 +216,7 @@ DataAccessObject.create = function (data, options, cb) {
var enforced = {}; var enforced = {};
var obj; var obj;
var idValue = getIdValue(this, data); var idValue = getIdValue(this, data);
var hookState = {};
// if we come from save // if we come from save
if (data instanceof Model && !idValue) { if (data instanceof Model && !idValue) {
@ -230,7 +231,9 @@ DataAccessObject.create = function (data, options, cb) {
Model = this.lookupModel(data); // data-specific Model = this.lookupModel(data); // data-specific
if (Model !== obj.constructor) obj = new Model(data); 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); if (err) return cb(err);
data = obj.toObject(true); data = obj.toObject(true);
@ -268,7 +271,9 @@ DataAccessObject.create = function (data, options, cb) {
if (err) { if (err) {
return cb(err, obj); 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); cb(err, obj);
if(!err) Model.emit('changed', obj); if(!err) Model.emit('changed', obj);
}); });
@ -345,20 +350,25 @@ DataAccessObject.updateOrCreate = DataAccessObject.upsert = function upsert(data
var self = this; var self = this;
var Model = this; var Model = this;
var hookState = {};
var id = getIdValue(this, data); var id = getIdValue(this, data);
if (!id) { if (!id) {
return this.create(data, options, cb); 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) { function doUpdateOrCreate(err, ctx) {
if (err) return cb(err); if (err) return cb(err);
var isOriginalQuery = isWhereByGivenId(Model, ctx.query.where, id) var isOriginalQuery = isWhereByGivenId(Model, ctx.query.where, id)
if (Model.getDataSource().connector.updateOrCreate && isOriginalQuery) { 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) { Model.notifyObserversOf('before save', context, function(err, ctx) {
if (err) return cb(err); if (err) return cb(err);
@ -394,7 +404,9 @@ DataAccessObject.updateOrCreate = DataAccessObject.upsert = function upsert(data
Model.emit('changed', inst); Model.emit('changed', inst);
} }
} else { } 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); cb(err, obj);
if(!err) { if(!err) {
Model.emit('changed', inst); Model.emit('changed', inst);
@ -478,6 +490,7 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb)
var Model = this; var Model = this;
var self = this; var self = this;
var hookState = {};
function _findOrCreate(query, data) { function _findOrCreate(query, data) {
var modelName = self.modelName; var modelName = self.modelName;
@ -493,8 +506,9 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb)
} }
if (created) { if (created) {
Model.notifyObserversOf('after save', { Model: Model, instance: obj }, Model.notifyObserversOf('after save', {
function(err) { Model: Model, instance: obj, hookState: hookState
}, function(err) {
if (cb.promise) { if (cb.promise) {
cb(err, [obj, created]); cb(err, [obj, created]);
} else { } else {
@ -526,8 +540,9 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb)
this.applyScope(query); this.applyScope(query);
Model.notifyObserversOf('access', { Model: Model, query: query }, Model.notifyObserversOf('access', {
function (err, ctx) { Model: Model, query: query, hookState: hookState
}, function (err, ctx) {
if (err) return cb(err); if (err) return cb(err);
var query = ctx.query; var query = ctx.query;
@ -539,8 +554,9 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb)
Model.applyProperties(enforced, obj); Model.applyProperties(enforced, obj);
obj.setAttributes(enforced); obj.setAttributes(enforced);
Model.notifyObserversOf('before save', { Model: Model, instance: obj }, Model.notifyObserversOf('before save', {
function(err, ctx) { Model: Model, instance: obj, hookState: hookState
}, function(err, ctx) {
if (err) return cb(err); if (err) return cb(err);
var obj = ctx.instance; 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'); assert(typeof cb === 'function', 'The cb argument must be a function');
var self = this; var self = this;
var hookState = {};
try { try {
this._normalize(query); this._normalize(query);
@ -1105,7 +1122,9 @@ DataAccessObject.find = function find(query, options, cb) {
// using all documents // using all documents
// TODO [fabien] use default scope here? // 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); if (err) return cb(err);
self.getDataSource().connector.all(self.modelName, {}, function (err, data) { self.getDataSource().connector.all(self.modelName, {}, function (err, data) {
@ -1199,7 +1218,9 @@ DataAccessObject.find = function find(query, options, cb) {
if (options.notify === false) { if (options.notify === false) {
self.getDataSource().connector.all(self.modelName, query, allCb); self.getDataSource().connector.all(self.modelName, query, allCb);
} else { } 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); if (err) return cb(err);
var query = ctx.query; var query = ctx.query;
self.getDataSource().connector.all(self.modelName, query, allCb); self.getDataSource().connector.all(self.modelName, query, allCb);
@ -1296,16 +1317,22 @@ DataAccessObject.remove = DataAccessObject.deleteAll = DataAccessObject.destroyA
this.applyScope(query); this.applyScope(query);
where = query.where; 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) { if (options.notify === false) {
doDelete(where); doDelete(where);
} else { } else {
query = { where: whereIsEmpty(where) ? {} : where }; query = { where: whereIsEmpty(where) ? {} : where };
Model.notifyObserversOf('access', Model.notifyObserversOf('access', {
{ Model: Model, query: query }, Model: Model, query: query, hookState: hookState
function(err, ctx) { }, function(err, ctx) {
if (err) return cb(err); 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) { Model.notifyObserversOf('before delete', context, function(err, ctx) {
if (err) return cb(err); if (err) return cb(err);
doDelete(ctx.where); doDelete(ctx.where);
@ -1338,7 +1365,9 @@ DataAccessObject.remove = DataAccessObject.deleteAll = DataAccessObject.destroyA
return cb(err, data); 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); cb(err, data);
if (!err) if (!err)
Model.emit('deletedAll', whereIsEmpty(where) ? undefined : where); Model.emit('deletedAll', whereIsEmpty(where) ? undefined : where);
@ -1459,7 +1488,11 @@ DataAccessObject.count = function (where, options, cb) {
} }
var Model = this; 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); if (err) return cb(err);
where = ctx.query.where; where = ctx.query.where;
Model.getDataSource().connector.count(Model.modelName, cb, where); Model.getDataSource().connector.count(Model.modelName, cb, where);
@ -1506,8 +1539,11 @@ DataAccessObject.prototype.save = function (options, cb) {
var inst = this; var inst = this;
var modelName = Model.modelName; 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); if (err) return cb(err);
var data = inst.toObject(true); var data = inst.toObject(true);
@ -1542,7 +1578,9 @@ DataAccessObject.prototype.save = function (options, cb) {
return cb(err, inst); return cb(err, inst);
} }
inst._initProperties(data, { persisted: true }); 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); if (err) return cb(err, inst);
updateDone.call(inst, function () { updateDone.call(inst, function () {
saveDone.call(inst, function () { saveDone.call(inst, function () {
@ -1625,15 +1663,19 @@ DataAccessObject.updateAll = function (where, data, options, cb) {
where = query.where; where = query.where;
var Model = this; 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); if (err) return cb(err);
Model.notifyObserversOf( Model.notifyObserversOf(
'before save', 'before save',
{ {
Model: Model, Model: Model,
where: ctx.query.where, where: ctx.query.where,
data: data data: data,
hookState: hookState
}, },
function(err, ctx) { function(err, ctx) {
if (err) return cb(err); if (err) return cb(err);
@ -1662,7 +1704,8 @@ DataAccessObject.updateAll = function (where, data, options, cb) {
{ {
Model: Model, Model: Model,
where: where, where: where,
data: data data: data,
hookState: hookState
}, },
function(err, ctx) { function(err, ctx) {
return cb(err, count); return cb(err, count);
@ -1714,16 +1757,15 @@ DataAccessObject.prototype.remove =
var self = this; var self = this;
var Model = this.constructor; var Model = this.constructor;
var id = getIdValue(this.constructor, this); var id = getIdValue(this.constructor, this);
var hookState = {};
Model.notifyObserversOf( Model.notifyObserversOf('access', {
'access', Model: Model, query: byIdQuery(Model, id), hookState: hookState
{ Model: Model, query: byIdQuery(Model, id) }, }, function(err, ctx) {
function(err, ctx) {
if (err) return cb(err); if (err) return cb(err);
Model.notifyObserversOf( Model.notifyObserversOf('before delete',{
'before delete', Model: Model, where: ctx.query.where, hookState: hookState
{ Model: Model, where: ctx.query.where }, }, function(err, ctx) {
function(err, ctx) {
if (err) return cb(err); if (err) return cb(err);
doDeleteInstance(ctx.where); doDeleteInstance(ctx.where);
}); });
@ -1736,7 +1778,9 @@ 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);
Model.notifyObserversOf('after delete', { Model: Model, where: where }, function(err) { Model.notifyObserversOf('after delete', {
Model: Model, where: where, hookState: hookState
}, function(err) {
cb(err); cb(err);
if (!err) Model.emit('deleted', id); if (!err) Model.emit('deleted', id);
}); });
@ -1751,7 +1795,9 @@ DataAccessObject.prototype.remove =
} }
destroyed(function () { 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); cb(err);
if (!err) Model.emit('deleted', id); if (!err) Model.emit('deleted', id);
}); });
@ -1858,6 +1904,7 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, op
var inst = this; var inst = this;
var Model = this.constructor; var Model = this.constructor;
var model = Model.modelName; var model = Model.modelName;
var hookState = {};
// Convert the data to be plain object so that update won't be confused // Convert the data to be plain object so that update won't be confused
if (data instanceof Model) { if (data instanceof Model) {
@ -1868,7 +1915,8 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, op
var context = { var context = {
Model: Model, Model: Model,
where: byIdQuery(Model, getIdValue(Model, inst)).where, where: byIdQuery(Model, getIdValue(Model, inst)).where,
data: data data: data,
hookState: hookState
}; };
Model.notifyObserversOf('before save', context, function(err, ctx) { Model.notifyObserversOf('before save', context, function(err, ctx) {
@ -1905,7 +1953,9 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, op
done.call(inst, function () { done.call(inst, function () {
saveDone.call(inst, function () { saveDone.call(inst, function () {
if (err) return cb(err, inst); 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); if(!err) Model.emit('changed', inst);
cb(err, inst); cb(err, inst);
}); });

View File

@ -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) { it('triggers hooks only once', function(done) {
TestModel.observe('access', pushNameAndNext('access')); TestModel.observe('access', pushNameAndNext('access'));
TestModel.observe('after delete', pushNameAndNext('after delete')); 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) { return function(context, next) {
if (typeof fn === 'function') {
fn(context);
}
context = deepCloneToObject(context); context = deepCloneToObject(context);
context.hookState.test = true;
if (typeof observedContexts === 'string') { if (typeof observedContexts === 'string') {
observedContexts = context; observedContexts = context;
@ -1246,6 +1276,9 @@ module.exports = function(dataSource, should) {
function aTestModelCtx(ctx) { function aTestModelCtx(ctx) {
ctx.Model = TestModel; ctx.Model = TestModel;
if (!ctx.hookState) {
ctx.hookState = { test: true };
}
return deepCloneToObject(ctx); return deepCloneToObject(ctx);
} }