From ffcaa4e76e040c5956e09ee8df32ad3dd914d4a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 19 Mar 2015 13:25:03 +0100 Subject: [PATCH] Add ctx.isNewInstance for "save" hooks "before save" hooks provide "ctx.isNewInstance" whenever "ctx.instance" is set. Possible values: - true for all CREATE operations - false for all UPDATE operations - undefined for "prototype.save" "after save" hooks provide "ctx.isNewInstance" whenever "ctx.instance" is set. Possible values: - true after all CREATE operations - false after all UPDATE operations - undefined after "updateOrCreate" and "save" Note: both "updateOrCreate" and "prototype.updateAttributes" don't provide `ctx.instance` to "before save" hooks, therefore `ctx.isNewInstance` it not provided either. --- lib/connectors/memory.js | 16 ++-- lib/dao.js | 53 +++++++++-- test/persistence-hooks.suite.js | 159 +++++++++++++++++++++----------- 3 files changed, 159 insertions(+), 69 deletions(-) diff --git a/lib/connectors/memory.js b/lib/connectors/memory.js index 22002c67..c68e6577 100644 --- a/lib/connectors/memory.js +++ b/lib/connectors/memory.js @@ -225,11 +225,13 @@ Memory.prototype.updateOrCreate = function (model, data, callback) { var self = this; this.exists(model, self.getIdValue(model, data), function (err, exists) { if (exists) { - self.save(model, data, callback); + self.save(model, data, function(err, data) { + callback(err, data, { isNewInstance: false }); + }); } else { self.create(model, data, function (err, id) { self.setIdValue(model, data, id); - callback(err, data); + callback(err, data, { isNewInstance: true }); }); } }); @@ -244,7 +246,9 @@ Memory.prototype.save = function save(model, data, callback) { data = merge(modelData, data); } this.collection(model)[id] = serialize(data); - this.saveToFile(data, callback); + this.saveToFile(data, function(err) { + callback(err, data, { isNewInstance: !modelData }); + }); }; Memory.prototype.exists = function exists(model, id, callback) { @@ -462,12 +466,12 @@ function applyFilter(filter) { if ('neq' in example) { return compare(example.neq, value) !== 0; } - + if ('between' in example ) { - return ( testInEquality({gte:example.between[0]}, value) && + return ( testInEquality({gte:example.between[0]}, value) && testInEquality({lte:example.between[1]}, value) ); } - + if (example.like || example.nlike) { var like = example.like || example.nlike; diff --git a/lib/dao.js b/lib/dao.js index 3b68a68c..5d7757ab 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -231,7 +231,12 @@ DataAccessObject.create = function (data, options, cb) { Model = this.lookupModel(data); // data-specific if (Model !== obj.constructor) obj = new Model(data); - var context = { Model: Model, instance: obj, hookState: hookState }; + var context = { + Model: Model, + instance: obj, + isNewInstance: true, + hookState: hookState + }; Model.notifyObserversOf('before save', context, function(err) { if (err) return cb(err); @@ -270,7 +275,12 @@ DataAccessObject.create = function (data, options, cb) { if (err) { return cb(err, obj); } - var context = { Model: Model, instance: obj, hookState: hookState }; + var context = { + Model: Model, + instance: obj, + isNewInstance: true, + hookState: hookState + }; Model.notifyObserversOf('after save', context, function(err) { cb(err, obj); if(!err) Model.emit('changed', obj); @@ -390,7 +400,7 @@ DataAccessObject.updateOrCreate = DataAccessObject.upsert = function upsert(data self.getDataSource().connector .updateOrCreate(Model.modelName, update, done); - function done(err, data) { + function done(err, data, result) { var obj; if (data && !(data instanceof Model)) { inst._initProperties(data, { persisted: true }); @@ -404,7 +414,12 @@ DataAccessObject.updateOrCreate = DataAccessObject.upsert = function upsert(data Model.emit('changed', inst); } } else { - var context = { Model: Model, instance: obj, hookState: hookState }; + var context = { + Model: Model, + instance: obj, + isNewInstance: result ? result.isNewInstance : undefined, + hookState: hookState + }; Model.notifyObserversOf('after save', context, function(err) { cb(err, obj); if(!err) { @@ -505,7 +520,12 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb) } if (created) { - var context = { Model: Model, instance: obj, hookState: hookState }; + var context = { + Model: Model, + instance: obj, + isNewInstance: true, + hookState: hookState + }; Model.notifyObserversOf('after save', context, function(err) { if (cb.promise) { cb(err, [obj, created]); @@ -551,7 +571,12 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb) Model.applyProperties(enforced, obj); obj.setAttributes(enforced); - var context = { Model: Model, instance: obj, hookState: hookState }; + var context = { + Model: Model, + instance: obj, + isNewInstance: true, + hookState: hookState + }; Model.notifyObserversOf('before save', context, function(err, ctx) { if (err) return cb(err); @@ -1562,12 +1587,17 @@ DataAccessObject.prototype.save = function (options, cb) { inst.trigger('save', function (saveDone) { inst.trigger('update', function (updateDone) { data = removeUndefined(data); - inst._adapter().save(modelName, inst.constructor._forDB(data), function (err) { + inst._adapter().save(modelName, inst.constructor._forDB(data), function (err, data, result) { if (err) { return cb(err, inst); } inst._initProperties(data, { persisted: true }); - var context = { Model: Model, instance: inst, hookState: hookState }; + var context = { + Model: Model, + instance: inst, + isNewInstance: result && result.isNewInstance, + hookState: hookState + }; Model.notifyObserversOf('after save', context, function(err) { if (err) return cb(err, inst); updateDone.call(inst, function () { @@ -1959,7 +1989,12 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, op done.call(inst, function () { saveDone.call(inst, function () { if (err) return cb(err, inst); - var context = { Model: Model, instance: inst, hookState: hookState }; + var context = { + Model: Model, + instance: inst, + isNewInstance: false, + hookState: hookState + }; Model.notifyObserversOf('after save', context, function(err) { if(!err) Model.emit('changed', inst); cb(err, inst); diff --git a/test/persistence-hooks.suite.js b/test/persistence-hooks.suite.js index d9e26184..63de035e 100644 --- a/test/persistence-hooks.suite.js +++ b/test/persistence-hooks.suite.js @@ -116,11 +116,14 @@ module.exports = function(dataSource, should) { TestModel.create({ name: 'created' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ instance: { - id: instance.id, - name: 'created', - extra: undefined - }})); + observedContexts.should.eql(aTestModelCtx({ + instance: { + id: instance.id, + name: 'created', + extra: undefined + }, + isNewInstance: true + })); done(); }); }); @@ -161,10 +164,12 @@ module.exports = function(dataSource, should) { }); observedContexts.should.eql([ aTestModelCtx({ - instance: { id: list[0].id, name: '1', extra: undefined } + instance: { id: list[0].id, name: '1', extra: undefined }, + isNewInstance: true }), aTestModelCtx({ - instance: { id: list[1].id, name: '2', extra: undefined } + instance: { id: list[1].id, name: '2', extra: undefined }, + isNewInstance: true }), ]); done(); @@ -186,11 +191,14 @@ module.exports = function(dataSource, should) { TestModel.create({ name: 'created' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ instance: { - id: instance.id, - name: 'created', - extra: undefined - }})); + observedContexts.should.eql(aTestModelCtx({ + instance: { + id: instance.id, + name: 'created', + extra: undefined + }, + isNewInstance: true + })); done(); }); }); @@ -231,10 +239,12 @@ module.exports = function(dataSource, should) { }); observedContexts.should.eql([ aTestModelCtx({ - instance: { id: list[0].id, name: '1', extra: undefined } + instance: { id: list[0].id, name: '1', extra: undefined }, + isNewInstance: true }), aTestModelCtx({ - instance: { id: list[1].id, name: '2', extra: undefined } + instance: { id: list[1].id, name: '2', extra: undefined }, + isNewInstance: true }), ]); done(); @@ -263,7 +273,8 @@ module.exports = function(dataSource, should) { list.map(get('name')).should.eql(['ok', 'fail']); observedContexts.should.eql(aTestModelCtx({ - instance: { id: list[0].id, name: 'ok', extra: undefined } + instance: { id: list[0].id, name: 'ok', extra: undefined }, + isNewInstance: true })); done(); }); @@ -299,11 +310,14 @@ module.exports = function(dataSource, should) { function(err, record, created) { if (err) return done(err); record.id.should.eql(existingInstance.id); - observedContexts.should.eql(aTestModelCtx({ instance: { - id: getLastGeneratedUid(), - name: existingInstance.name, - extra: undefined - }})); + observedContexts.should.eql(aTestModelCtx({ + instance: { + id: getLastGeneratedUid(), + name: existingInstance.name, + extra: undefined + }, + isNewInstance: true + })); done(); }); }); @@ -317,11 +331,14 @@ module.exports = function(dataSource, should) { { name: 'new-record' }, function(err, record, created) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ instance: { - id: record.id, - name: 'new-record', - extra: undefined - }})); + observedContexts.should.eql(aTestModelCtx({ + instance: { + id: record.id, + name: 'new-record', + extra: undefined + }, + isNewInstance: true + })); done(); }); }); @@ -393,11 +410,14 @@ module.exports = function(dataSource, should) { { name: 'new name' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ instance: { - id: instance.id, - name: 'new name', - extra: undefined - }})); + observedContexts.should.eql(aTestModelCtx({ + instance: { + id: instance.id, + name: 'new name', + extra: undefined + }, + isNewInstance: true + })); done(); }); }); @@ -492,17 +512,38 @@ module.exports = function(dataSource, should) { }); }); - it('triggers `after save` hook', function(done) { + it('triggers `after save` hook on update', function(done) { TestModel.observe('after save', pushContextAndNext()); existingInstance.name = 'changed'; existingInstance.save(function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ instance: { - id: existingInstance.id, - name: 'changed', - extra: undefined - }})); + observedContexts.should.eql(aTestModelCtx({ + instance: { + id: existingInstance.id, + name: 'changed', + extra: undefined + }, + isNewInstance: false + })); + done(); + }); + }); + + it('triggers `after save` hook on create', function(done) { + TestModel.observe('after save', pushContextAndNext()); + + var instance = new TestModel({ name: 'created' }); + instance.save(function(err, instance) { + if (err) return done(err); + observedContexts.should.eql(aTestModelCtx({ + instance: { + id: instance.id, + name: 'created', + extra: undefined + }, + isNewInstance: true + })); done(); }); }); @@ -597,11 +638,14 @@ module.exports = function(dataSource, should) { existingInstance.name = 'changed'; existingInstance.updateAttributes({ name: 'changed' }, function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ instance: { - id: existingInstance.id, - name: 'changed', - extra: undefined - }})); + observedContexts.should.eql(aTestModelCtx({ + instance: { + id: existingInstance.id, + name: 'changed', + extra: undefined + }, + isNewInstance: false + })); done(); }); }); @@ -781,7 +825,8 @@ module.exports = function(dataSource, should) { // The default unoptimized implementation runs // `instance.save` and thus a full instance is availalbe observedContexts.should.eql(aTestModelCtx({ - instance: { id: 'new-id', name: 'a name', extra: undefined } + instance: { id: 'new-id', name: 'a name', extra: undefined }, + isNewInstance: true })); } @@ -859,11 +904,14 @@ module.exports = function(dataSource, should) { { id: existingInstance.id, name: 'updated name' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ instance: { - id: existingInstance.id, - name: 'updated name', - extra: undefined - }})); + observedContexts.should.eql(aTestModelCtx({ + instance: { + id: existingInstance.id, + name: 'updated name', + extra: undefined + }, + isNewInstance: false + })); done(); }); }); @@ -875,11 +923,14 @@ module.exports = function(dataSource, should) { { id: 'new-id', name: 'a name' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ instance: { - id: instance.id, - name: 'a name', - extra: undefined - }})); + observedContexts.should.eql(aTestModelCtx({ + instance: { + id: instance.id, + name: 'a name', + extra: undefined + }, + isNewInstance: true + })); done(); }); }); @@ -1126,12 +1177,12 @@ module.exports = function(dataSource, should) { existingInstance.delete(function(err) { if (err) return done(err); observedContexts.should.eql([ - aTestModelCtx({ + aTestModelCtx({ hookState: { foo: 'bar', test: true }, where: { id: '1' }, instance: existingInstance }), - aTestModelCtx({ + aTestModelCtx({ hookState: { foo: 'BAR', test: true }, where: { id: '1' }, instance: existingInstance