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.
This commit is contained in:
Miroslav Bajtoš 2015-03-19 13:25:03 +01:00
parent ae3dc3cec2
commit ffcaa4e76e
3 changed files with 159 additions and 69 deletions

View File

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

View File

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

View File

@ -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