From 1ab3d74ab5a49b132ff9851f3cbcb7b284141883 Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Mon, 30 Mar 2015 15:03:45 +0200 Subject: [PATCH] Pass options in operation hooks context. --- lib/dao.js | 160 ++++++++++++++++++++++++-------- test/persistence-hooks.suite.js | 28 +++++- 2 files changed, 146 insertions(+), 42 deletions(-) diff --git a/lib/dao.js b/lib/dao.js index ce3cad70..da7a09f1 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -178,6 +178,8 @@ DataAccessObject.create = function (data, options, cb) { assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); + var hookState = {}; + if (Array.isArray(data)) { // Undefined item will be skipped by async.map() which internally uses // Array.prototype.map(). The following loop makes sure all items are @@ -216,7 +218,6 @@ 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) { @@ -235,7 +236,8 @@ DataAccessObject.create = function (data, options, cb) { Model: Model, instance: obj, isNewInstance: true, - hookState: hookState + hookState: hookState, + options: options }; Model.notifyObserversOf('before save', context, function(err) { if (err) return cb(err); @@ -279,7 +281,8 @@ DataAccessObject.create = function (data, options, cb) { Model: Model, instance: obj, isNewInstance: true, - hookState: hookState + hookState: hookState, + options: options }; Model.notifyObserversOf('after save', context, function(err) { cb(err, obj); @@ -356,16 +359,22 @@ DataAccessObject.updateOrCreate = DataAccessObject.upsert = function upsert(data assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); + var hookState = {}; + var self = this; var Model = this; - var hookState = {}; var id = getIdValue(this, data); if (!id) { return this.create(data, options, cb); } - var context = { Model: Model, query: byIdQuery(Model, id), hookState: hookState }; + var context = { + Model: Model, + query: byIdQuery(Model, id), + hookState: hookState, + options: options + }; Model.notifyObserversOf('access', context, doUpdateOrCreate); function doUpdateOrCreate(err, ctx) { @@ -377,7 +386,8 @@ DataAccessObject.updateOrCreate = DataAccessObject.upsert = function upsert(data Model: Model, where: ctx.query.where, data: data, - hookState: hookState + hookState: hookState, + options: options }; Model.notifyObserversOf('before save', context, function(err, ctx) { if (err) return cb(err); @@ -418,7 +428,8 @@ DataAccessObject.updateOrCreate = DataAccessObject.upsert = function upsert(data Model: Model, instance: obj, isNewInstance: result ? result.isNewInstance : undefined, - hookState: hookState + hookState: hookState, + options: options }; Model.notifyObserversOf('after save', context, function(err) { cb(err, obj); @@ -502,9 +513,10 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb) assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); + var hookState = {}; + var Model = this; var self = this; - var hookState = {}; function _findOrCreate(query, data) { var modelName = self.modelName; @@ -524,7 +536,8 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb) Model: Model, instance: obj, isNewInstance: true, - hookState: hookState + hookState: hookState, + options: options }; Model.notifyObserversOf('after save', context, function(err) { if (cb.promise) { @@ -558,7 +571,12 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb) this.applyScope(query); - var context = { Model: Model, query: query, hookState: hookState }; + var context = { + Model: Model, + query: query, + hookState: hookState, + options: options + }; Model.notifyObserversOf('access', context, function (err, ctx) { if (err) return cb(err); @@ -575,7 +593,8 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb) Model: Model, instance: obj, isNewInstance: true, - hookState: hookState + hookState: hookState, + options: options }; Model.notifyObserversOf('before save', context, function(err, ctx) { if (err) return cb(err); @@ -1117,8 +1136,8 @@ DataAccessObject.find = function find(query, options, cb) { assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); - var self = this; var hookState = {}; + var self = this; try { this._normalize(query); @@ -1143,7 +1162,12 @@ DataAccessObject.find = function find(query, options, cb) { // using all documents // TODO [fabien] use default scope here? - var context = { Model: self, query: query, hookState: hookState }; + var context = { + Model: self, + query: query, + hookState: hookState, + options: options + }; self.notifyObserversOf('access', context, function(err, ctx) { if (err) return cb(err); @@ -1238,7 +1262,12 @@ DataAccessObject.find = function find(query, options, cb) { if (options.notify === false) { self.getDataSource().connector.all(self.modelName, query, allCb); } else { - var context = { Model: this, query: query, hookState: hookState }; + var context = { + Model: this, + query: query, + hookState: hookState, + options: options + }; this.notifyObserversOf('access', context, function(err, ctx) { if (err) return cb(err); var query = ctx.query; @@ -1327,29 +1356,42 @@ DataAccessObject.remove = DataAccessObject.deleteAll = DataAccessObject.destroyA cb = cb || utils.createPromiseCallback(); where = where || {}; options = options || {}; - + assert(typeof where === 'object', 'The where argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); + + var hookState = {}; var query = { where: where }; this.applyScope(query); where = query.where; - var hookState = {}; - var context = { - Model: Model, where: whereIsEmpty(where) ? {} : where, hookState: hookState + Model: Model, + where: whereIsEmpty(where) ? {} : where, + hookState: hookState, + options: options }; if (options.notify === false) { doDelete(where); } else { query = { where: whereIsEmpty(where) ? {} : where }; - var context = { Model: Model, query: query, hookState: hookState }; + var context = { + Model: Model, + query: query, + hookState: hookState, + options: options + }; 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, + hookState: hookState, + options: options + }; Model.notifyObserversOf('before delete', context, function(err, ctx) { if (err) return cb(err); doDelete(ctx.where); @@ -1382,7 +1424,12 @@ DataAccessObject.remove = DataAccessObject.deleteAll = DataAccessObject.destroyA return cb(err, data); } - var context = { Model: Model, where: where, hookState: hookState }; + var context = { + Model: Model, + where: where, + hookState: hookState, + options: options + }; Model.notifyObserversOf('after delete', context, function(err) { cb(err, data); if (!err) @@ -1489,6 +1536,8 @@ DataAccessObject.count = function (where, options, cb) { assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); + var hookState = {}; + var query = { where: where }; this.applyScope(query); where = query.where; @@ -1504,9 +1553,13 @@ DataAccessObject.count = function (where, options, cb) { } var Model = this; - var hookState = {}; - var context = { Model: Model, query: { where: where }, hookState: hookState }; + var context = { + Model: Model, + query: { where: where }, + hookState: hookState, + options: options + }; this.notifyObserversOf('access', context, function(err, ctx) { if (err) return cb(err); where = ctx.query.where; @@ -1540,6 +1593,8 @@ DataAccessObject.prototype.save = function (options, cb) { assert(typeof options === 'object', 'The options argument should be an object'); assert(typeof cb === 'function', 'The cb argument should be a function'); + + var hookState = {}; if (options.validate === undefined) { options.validate = true; @@ -1554,9 +1609,13 @@ DataAccessObject.prototype.save = function (options, cb) { var inst = this; var modelName = Model.modelName; - var hookState = {}; - var context = { Model: Model, instance: inst, hookState: hookState }; + var context = { + Model: Model, + instance: inst, + hookState: hookState, + options: options + }; Model.notifyObserversOf('before save', context, function(err) { if (err) return cb(err); @@ -1596,7 +1655,8 @@ DataAccessObject.prototype.save = function (options, cb) { Model: Model, instance: inst, isNewInstance: result && result.isNewInstance, - hookState: hookState + hookState: hookState, + options: options }; Model.notifyObserversOf('after save', context, function(err) { if (err) return cb(err, inst); @@ -1674,6 +1734,8 @@ DataAccessObject.updateAll = function (where, data, options, cb) { assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); + var hookState = {}; + var query = { where: where }; this.applyScope(query); this.applyProperties(data); @@ -1681,13 +1743,21 @@ DataAccessObject.updateAll = function (where, data, options, cb) { where = query.where; var Model = this; - var hookState = {}; - var context = { Model: Model, query: { where: where }, hookState: hookState }; + var context = { + Model: Model, + query: { where: where }, + hookState: hookState, + options: options + }; Model.notifyObserversOf('access', context, function(err, ctx) { if (err) return cb(err); var context = { - Model: Model, where: ctx.query.where, data: data, hookState: hookState + Model: Model, + where: ctx.query.where, + data: data, + hookState: hookState, + options: options }; Model.notifyObserversOf('before save', context, function(err, ctx) { @@ -1713,7 +1783,11 @@ DataAccessObject.updateAll = function (where, data, options, cb) { connector.update(Model.modelName, where, data, function(err, count) { if (err) return cb (err); var context = { - Model: Model, where: where, data: data, hookState: hookState + Model: Model, + where: where, + data: data, + hookState: hookState, + options: options }; Model.notifyObserversOf('after save', context, function(err, ctx) { return cb(err, count); @@ -1762,13 +1836,17 @@ 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 hookState = {}; + 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, + options: options }; Model.notifyObserversOf('access', context, function(err, ctx) { @@ -1777,7 +1855,8 @@ DataAccessObject.prototype.remove = Model: Model, where: ctx.query.where, instance: inst, - hookState: hookState + hookState: hookState, + options: options }; Model.notifyObserversOf('before delete', context, function(err, ctx) { if (err) return cb(err); @@ -1796,7 +1875,8 @@ DataAccessObject.prototype.remove = Model: Model, where: where, instance: inst, - hookState: hookState + hookState: hookState, + options: options }; Model.notifyObserversOf('after delete', context, function(err) { cb(err); @@ -1817,7 +1897,8 @@ DataAccessObject.prototype.remove = Model: Model, where: where, instance: inst, - hookState: hookState + hookState: hookState, + options: options }; Model.notifyObserversOf('after delete', context, function(err) { cb(err); @@ -1944,10 +2025,11 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, op assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); + var hookState = {}; + 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) { @@ -1974,7 +2056,8 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, op where: byIdQuery(Model, getIdValue(Model, inst)).where, data: data, currentInstance: inst, - hookState: hookState + hookState: hookState, + options: options }; Model.notifyObserversOf('before save', context, function(err, ctx) { @@ -2015,7 +2098,8 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, op Model: Model, instance: inst, isNewInstance: false, - hookState: hookState + hookState: hookState, + options: options }; Model.notifyObserversOf('after save', context, function(err) { if(!err) Model.emit('changed', inst); diff --git a/test/persistence-hooks.suite.js b/test/persistence-hooks.suite.js index 63de035e..f2db5b01 100644 --- a/test/persistence-hooks.suite.js +++ b/test/persistence-hooks.suite.js @@ -473,8 +473,8 @@ module.exports = function(dataSource, should) { observedContexts.should.eql(aTestModelCtx({ instance: { id: existingInstance.id, name: 'changed', - extra: undefined - }})); + extra: undefined, + }, options: { throws: false, validate: true } })); done(); }); }); @@ -524,8 +524,8 @@ module.exports = function(dataSource, should) { name: 'changed', extra: undefined }, - isNewInstance: false - })); + isNewInstance: false, + options: { throws: false, validate: true } })); done(); }); }); @@ -1297,6 +1297,23 @@ module.exports = function(dataSource, should) { done(); }); }); + + it('accepts hookState from options', function(done) { + TestModel.observe('after save', pushContextAndNext()); + + TestModel.updateAll( + { id: existingInstance.id }, + { name: 'updated name' }, + { foo: 'bar' }, + function(err) { + if (err) return done(err); + observedContexts.options.should.eql({ + foo: 'bar' + }); + done(); + }); + }); + }); function pushContextAndNext(fn) { @@ -1351,6 +1368,9 @@ module.exports = function(dataSource, should) { if (!ctx.hookState) { ctx.hookState = { test: true }; } + if (!ctx.options) { + ctx.options = {}; + } return deepCloneToObject(ctx); }