Merge pull request #586 from PradnyaBaviskar/issue-559
Add new hook `persist`
This commit is contained in:
commit
ec597ef207
100
lib/dao.js
100
lib/dao.js
|
@ -284,6 +284,7 @@ DataAccessObject.create = function (data, options, cb) {
|
|||
return cb(err, obj);
|
||||
}
|
||||
obj.__persisted = true;
|
||||
|
||||
saveDone.call(obj, function () {
|
||||
createDone.call(obj, function () {
|
||||
if (err) {
|
||||
|
@ -304,11 +305,23 @@ DataAccessObject.create = function (data, options, cb) {
|
|||
});
|
||||
}
|
||||
|
||||
context = {
|
||||
Model: Model,
|
||||
data: val,
|
||||
isNewInstance: true,
|
||||
currentInstance: obj,
|
||||
hookState: hookState,
|
||||
options: options
|
||||
};
|
||||
Model.notifyObserversOf('persist', context, function(err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
if (connector.create.length === 4) {
|
||||
connector.create(modelName, this.constructor._forDB(val), options, createCallback);
|
||||
connector.create(modelName, obj.constructor._forDB(context.data), options, createCallback);
|
||||
} else {
|
||||
connector.create(modelName, this.constructor._forDB(val), createCallback);
|
||||
connector.create(modelName, obj.constructor._forDB(context.data), createCallback);
|
||||
}
|
||||
});
|
||||
}, obj, cb);
|
||||
}, obj, cb);
|
||||
}
|
||||
|
@ -425,12 +438,7 @@ DataAccessObject.updateOrCreate = DataAccessObject.upsert = function upsert(data
|
|||
var connector = self.getConnector();
|
||||
|
||||
if (Model.settings.validateUpsert === false) {
|
||||
update = removeUndefined(update);
|
||||
if (connector.updateOrCreate.length === 4) {
|
||||
connector.updateOrCreate(Model.modelName, update, options, done);
|
||||
} else {
|
||||
connector.updateOrCreate(Model.modelName, update, done);
|
||||
}
|
||||
callConnector();
|
||||
} else {
|
||||
inst.isValid(function(valid) {
|
||||
if (!valid) {
|
||||
|
@ -443,16 +451,29 @@ DataAccessObject.updateOrCreate = DataAccessObject.upsert = function upsert(data
|
|||
// continue with updateOrCreate
|
||||
}
|
||||
}
|
||||
callConnector();
|
||||
}, update);
|
||||
}
|
||||
|
||||
function callConnector() {
|
||||
update = removeUndefined(update);
|
||||
context = {
|
||||
Model: Model,
|
||||
where: ctx.where,
|
||||
data: update,
|
||||
currentInstance: inst,
|
||||
hookState: ctx.hookState,
|
||||
options: options
|
||||
};
|
||||
Model.notifyObserversOf('persist', context, function(err) {
|
||||
if (err) return done(err);
|
||||
if (connector.updateOrCreate.length === 4) {
|
||||
connector.updateOrCreate(Model.modelName, update, options, done);
|
||||
} else {
|
||||
connector.updateOrCreate(Model.modelName, update, done);
|
||||
}
|
||||
}, update);
|
||||
});
|
||||
}
|
||||
|
||||
function done(err, data, info) {
|
||||
var obj;
|
||||
if (data && !(data instanceof Model)) {
|
||||
|
@ -562,9 +583,8 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb)
|
|||
var self = this;
|
||||
var connector = Model.getConnector();
|
||||
|
||||
function _findOrCreate(query, data) {
|
||||
function _findOrCreate(query, data, currentInstance) {
|
||||
var modelName = self.modelName;
|
||||
data = removeUndefined(data);
|
||||
function findOrCreateCallback(err, data, created) {
|
||||
var obj, Model = self.lookupModel(data);
|
||||
|
||||
|
@ -598,11 +618,26 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb)
|
|||
}
|
||||
}
|
||||
|
||||
data = removeUndefined(data);
|
||||
var context = {
|
||||
Model: Model,
|
||||
where: query.where,
|
||||
data: data,
|
||||
isNewInstance: true,
|
||||
currentInstance : currentInstance,
|
||||
hookState: hookState,
|
||||
options: options
|
||||
};
|
||||
|
||||
Model.notifyObserversOf('persist', context, function(err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
if (connector.findOrCreate.length === 5) {
|
||||
connector.findOrCreate(modelName, query, self._forDB(data), options, findOrCreateCallback);
|
||||
connector.findOrCreate(modelName, query, self._forDB(context.data), options, findOrCreateCallback);
|
||||
} else {
|
||||
connector.findOrCreate(modelName, query, self._forDB(data), findOrCreateCallback);
|
||||
connector.findOrCreate(modelName, query, self._forDB(context.data), findOrCreateCallback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (connector.findOrCreate) {
|
||||
|
@ -653,7 +688,7 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb)
|
|||
// validation required
|
||||
obj.isValid(function (valid) {
|
||||
if (valid) {
|
||||
_findOrCreate(query, data);
|
||||
_findOrCreate(query, data, obj);
|
||||
} else {
|
||||
cb(new ValidationError(obj), obj);
|
||||
}
|
||||
|
@ -1789,11 +1824,25 @@ DataAccessObject.prototype.save = function (options, cb) {
|
|||
});
|
||||
}
|
||||
|
||||
context = {
|
||||
Model: Model,
|
||||
data: data,
|
||||
where: byIdQuery(Model, getIdValue(Model, inst)).where,
|
||||
currentInstance: inst,
|
||||
hookState: hookState,
|
||||
options: options
|
||||
};
|
||||
|
||||
Model.notifyObserversOf('persist', context, function(err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
if (connector.save.length === 4) {
|
||||
connector.save(modelName, inst.constructor._forDB(data), options, saveCallback);
|
||||
} else {
|
||||
connector.save(modelName, inst.constructor._forDB(data), saveCallback);
|
||||
}
|
||||
});
|
||||
|
||||
}, data, cb);
|
||||
}, data, cb);
|
||||
}
|
||||
|
@ -1919,11 +1968,22 @@ DataAccessObject.updateAll = function (where, data, options, cb) {
|
|||
});
|
||||
}
|
||||
|
||||
var context = {
|
||||
Model: Model,
|
||||
where: where,
|
||||
data: data,
|
||||
hookState: hookState,
|
||||
options: options
|
||||
};
|
||||
Model.notifyObserversOf('persist', context, function(err, ctx) {
|
||||
if (err) return cb (err);
|
||||
|
||||
if (connector.update.length === 5) {
|
||||
connector.update(Model.modelName, where, data, options, updateCallback);
|
||||
} else {
|
||||
connector.update(Model.modelName, where, data, updateCallback);
|
||||
}
|
||||
});
|
||||
}
|
||||
return cb.promise;
|
||||
};
|
||||
|
@ -2249,6 +2309,15 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, op
|
|||
});
|
||||
}
|
||||
|
||||
context = {
|
||||
Model: Model,
|
||||
where: byIdQuery(Model, getIdValue(Model, inst)).where,
|
||||
data: data,
|
||||
currentInstance: inst,
|
||||
hookState: hookState,
|
||||
options: options
|
||||
};
|
||||
Model.notifyObserversOf('persist', context, function(err) {
|
||||
if (connector.updateAttributes.length === 5) {
|
||||
connector.updateAttributes(model, getIdValue(inst.constructor, inst),
|
||||
inst.constructor._forDB(typedData), options, updateAttributesCallback);
|
||||
|
@ -2256,6 +2325,7 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, op
|
|||
connector.updateAttributes(model, getIdValue(inst.constructor, inst),
|
||||
inst.constructor._forDB(typedData), updateAttributesCallback);
|
||||
}
|
||||
});
|
||||
}, data, cb);
|
||||
}, data, cb);
|
||||
}, data);
|
||||
|
|
|
@ -186,6 +186,53 @@ module.exports = function(dataSource, should) {
|
|||
});
|
||||
});
|
||||
|
||||
it('triggers `persist` hook', function(done) {
|
||||
TestModel.observe('persist', pushContextAndNext());
|
||||
|
||||
TestModel.create(
|
||||
{ id: 'new-id', name: 'a name' },
|
||||
function(err, instance) {
|
||||
if (err) return done(err);
|
||||
|
||||
observedContexts.should.eql(aTestModelCtx({
|
||||
data: { id: 'new-id', name: 'a name' },
|
||||
isNewInstance: true,
|
||||
currentInstance: { extra: null, id: 'new-id', name: 'a name' }
|
||||
}));
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('applies updates from `persist` hook', function(done) {
|
||||
TestModel.observe('persist', pushContextAndNext(function(ctx){
|
||||
ctx.data.extra = 'hook data';
|
||||
}));
|
||||
|
||||
TestModel.create(
|
||||
{ id: 'new-id', name: 'a name' },
|
||||
function(err, instance) {
|
||||
if (err) return done(err);
|
||||
|
||||
// the, instance returned by `create` context does not have the
|
||||
// values updated from `persist` hook
|
||||
instance.should.not.have.property('extra', 'hook data');
|
||||
|
||||
// So, we must query the database here because on `create`
|
||||
// updates from `persist` hook are reflected into database
|
||||
TestModel.findById('new-id', function(err, dbInstance) {
|
||||
if (err) return done(err);
|
||||
dbInstance.toObject(true).should.eql({
|
||||
id: 'new-id',
|
||||
name: 'a name',
|
||||
extra: 'hook data'
|
||||
});
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('triggers `after save` hook', function(done) {
|
||||
TestModel.observe('after save', pushContextAndNext());
|
||||
|
||||
|
@ -372,6 +419,7 @@ module.exports = function(dataSource, should) {
|
|||
triggered.should.eql([
|
||||
'access',
|
||||
'before save',
|
||||
'persist',
|
||||
'after save'
|
||||
]);
|
||||
done();
|
||||
|
@ -402,6 +450,151 @@ module.exports = function(dataSource, should) {
|
|||
});
|
||||
});
|
||||
|
||||
if (dataSource.connector.findOrCreate) {
|
||||
it('triggers `persist` hook when found', function(done) {
|
||||
TestModel.observe('persist', pushContextAndNext());
|
||||
|
||||
TestModel.findOrCreate(
|
||||
{ where: { name: existingInstance.name } },
|
||||
{ name: existingInstance.name },
|
||||
function(err, record, created) {
|
||||
if (err) return done(err);
|
||||
|
||||
record.id.should.eql(existingInstance.id);
|
||||
|
||||
// `findOrCreate` creates a new instance of the object everytime.
|
||||
// So, `data.id` as well as `currentInstance.id` always matches
|
||||
// the newly generated UID.
|
||||
// Hence, the test below asserts both `data.id` and
|
||||
// `currentInstance.id` to match getLastGeneratedUid().
|
||||
// On same lines, it also asserts `isNewInstance` to be true.
|
||||
observedContexts.should.eql(aTestModelCtx({
|
||||
data: {
|
||||
id: getLastGeneratedUid(),
|
||||
name: existingInstance.name
|
||||
},
|
||||
isNewInstance: true,
|
||||
currentInstance: {
|
||||
id: getLastGeneratedUid(),
|
||||
name: record.name,
|
||||
extra: null
|
||||
},
|
||||
where: { name: existingInstance.name }
|
||||
}));
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it('triggers `persist` hook when not found', function(done) {
|
||||
TestModel.observe('persist', pushContextAndNext());
|
||||
|
||||
TestModel.findOrCreate(
|
||||
{ where: { name: 'new-record' } },
|
||||
{ name: 'new-record' },
|
||||
function(err, record, created) {
|
||||
if (err) return done(err);
|
||||
|
||||
// `context.where` is present in Optimized connector context,
|
||||
// but, unoptimized connector does NOT have it.
|
||||
if (dataSource.connector.findOrCreate) {
|
||||
observedContexts.should.eql(aTestModelCtx({
|
||||
data: {
|
||||
id: record.id,
|
||||
name: 'new-record'
|
||||
},
|
||||
isNewInstance: true,
|
||||
currentInstance: {
|
||||
id: record.id,
|
||||
name: record.name,
|
||||
extra: null
|
||||
},
|
||||
where: { name: 'new-record' }
|
||||
}));
|
||||
} else {
|
||||
observedContexts.should.eql(aTestModelCtx({
|
||||
data: {
|
||||
id: record.id,
|
||||
name: 'new-record'
|
||||
},
|
||||
isNewInstance: true,
|
||||
currentInstance: { id: record.id, name: record.name, extra: null }
|
||||
}));
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
if (dataSource.connector.findOrCreate) {
|
||||
it('applies updates from `persist` hook when found', function(done) {
|
||||
TestModel.observe('persist', pushContextAndNext(function(ctx){
|
||||
ctx.data.extra = 'hook data';
|
||||
}));
|
||||
|
||||
TestModel.findOrCreate(
|
||||
{ where: { name: existingInstance.name } },
|
||||
{ name: existingInstance.name },
|
||||
function(err, instance) {
|
||||
if (err) return done(err);
|
||||
|
||||
// instance returned by `findOrCreate` context does not
|
||||
// have the values updated from `persist` hook
|
||||
instance.should.not.have.property('extra', 'hook data');
|
||||
|
||||
// Query the database. Here, since record already exists
|
||||
// `findOrCreate`, does not update database for
|
||||
// updates from `persist` hook
|
||||
TestModel.findById(existingInstance.id, function(err, dbInstance) {
|
||||
if (err) return done(err);
|
||||
dbInstance.toObject(true).should.eql({
|
||||
id: existingInstance.id,
|
||||
name: existingInstance.name,
|
||||
extra: undefined
|
||||
});
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it('applies updates from `persist` hook when not found', function(done) {
|
||||
TestModel.observe('persist', pushContextAndNext(function(ctx){
|
||||
ctx.data.extra = 'hook data';
|
||||
}));
|
||||
|
||||
TestModel.findOrCreate(
|
||||
{ where: { name: 'new-record' } },
|
||||
{ name: 'new-record' },
|
||||
function(err, instance) {
|
||||
if (err) return done(err);
|
||||
|
||||
if (dataSource.connector.findOrCreate) {
|
||||
instance.should.have.property('extra', 'hook data');
|
||||
} else {
|
||||
// Unoptimized connector gives a call to `create. And during
|
||||
// create the updates applied through persist hook are
|
||||
// reflected into the database, but the same updates are
|
||||
// NOT reflected in the instance object obtained in callback
|
||||
// of create.
|
||||
// So, this test asserts unoptimized connector to
|
||||
// NOT have `extra` property. And then verifes that the
|
||||
// property `extra` is actually updated in DB
|
||||
instance.should.not.have.property('extra', 'hook data');
|
||||
TestModel.findById(instance.id, function(err, dbInstance) {
|
||||
if (err) return done(err);
|
||||
dbInstance.toObject(true).should.eql({
|
||||
id: instance.id,
|
||||
name: instance.name,
|
||||
extra: 'hook data'
|
||||
});
|
||||
});
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('triggers `after save` hook when not found', function(done) {
|
||||
TestModel.observe('after save', pushContextAndNext());
|
||||
|
||||
|
@ -512,6 +705,43 @@ module.exports = function(dataSource, should) {
|
|||
});
|
||||
});
|
||||
|
||||
it('triggers `persist` hook', function(done) {
|
||||
TestModel.observe('persist', pushContextAndNext());
|
||||
|
||||
existingInstance.name = 'changed';
|
||||
existingInstance.save(function(err, instance) {
|
||||
if (err) return done(err);
|
||||
|
||||
observedContexts.should.eql(aTestModelCtx({
|
||||
data: {
|
||||
id: existingInstance.id,
|
||||
name: 'changed'
|
||||
},
|
||||
currentInstance: {
|
||||
id: existingInstance.id,
|
||||
name: 'changed',
|
||||
extra: undefined
|
||||
},
|
||||
where: { id: existingInstance.id },
|
||||
options: { throws: false, validate: true }
|
||||
}));
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('applies updates from `persist` hook', function(done) {
|
||||
TestModel.observe('persist', pushContextAndNext(function(ctx){
|
||||
ctx.data.extra = 'hook data';
|
||||
}));
|
||||
|
||||
existingInstance.save(function(err, instance) {
|
||||
if (err) return done(err);
|
||||
instance.should.have.property('extra', 'hook data');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('triggers `after save` hook on update', function(done) {
|
||||
TestModel.observe('after save', pushContextAndNext());
|
||||
|
||||
|
@ -637,6 +867,37 @@ module.exports = function(dataSource, should) {
|
|||
});
|
||||
});
|
||||
|
||||
it('triggers `persist` hook', function(done) {
|
||||
TestModel.observe('persist', pushContextAndNext());
|
||||
existingInstance.updateAttributes({ name: 'changed' }, function(err) {
|
||||
if (err) return done(err);
|
||||
|
||||
observedContexts.should.eql(aTestModelCtx({
|
||||
where: { id: existingInstance.id },
|
||||
data: { name: 'changed' },
|
||||
currentInstance: {
|
||||
id: existingInstance.id,
|
||||
name: 'changed',
|
||||
extra: null
|
||||
}
|
||||
}));
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('applies updates from `persist` hook', function(done) {
|
||||
TestModel.observe('persist', pushContextAndNext(function(ctx){
|
||||
ctx.data.extra = 'hook data';
|
||||
}));
|
||||
|
||||
existingInstance.save(function(err, instance) {
|
||||
if (err) return done(err);
|
||||
instance.should.have.property('extra', 'hook data');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('triggers `after save` hook', function(done) {
|
||||
TestModel.observe('after save', pushContextAndNext());
|
||||
|
||||
|
@ -901,6 +1162,66 @@ module.exports = function(dataSource, should) {
|
|||
});
|
||||
});
|
||||
|
||||
it('triggers `persist` hook on create', function(done) {
|
||||
TestModel.observe('persist', pushContextAndNext());
|
||||
|
||||
TestModel.updateOrCreate(
|
||||
{ id: 'new-id', name: 'a name' },
|
||||
function(err, instance) {
|
||||
if (err) return done(err);
|
||||
|
||||
if (dataSource.connector.updateOrCreate) {
|
||||
observedContexts.should.eql(aTestModelCtx({
|
||||
where: { id: 'new-id' },
|
||||
data: { id: 'new-id', name: 'a name' },
|
||||
currentInstance: {
|
||||
id: 'new-id',
|
||||
name: 'a name',
|
||||
extra: undefined
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
observedContexts.should.eql(aTestModelCtx({
|
||||
data: {
|
||||
id: 'new-id',
|
||||
name: 'a name'
|
||||
},
|
||||
isNewInstance: true,
|
||||
currentInstance: {
|
||||
id: 'new-id',
|
||||
name: 'a name',
|
||||
extra: undefined
|
||||
}
|
||||
}));
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('triggers `persist` hook on update', function(done) {
|
||||
TestModel.observe('persist', pushContextAndNext());
|
||||
|
||||
TestModel.updateOrCreate(
|
||||
{ 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'
|
||||
},
|
||||
currentInstance: {
|
||||
id: existingInstance.id,
|
||||
name: 'updated name',
|
||||
extra: undefined
|
||||
}
|
||||
}));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('triggers `after save` hook on update', function(done) {
|
||||
TestModel.observe('after save', pushContextAndNext());
|
||||
|
@ -1287,6 +1608,41 @@ module.exports = function(dataSource, should) {
|
|||
});
|
||||
});
|
||||
|
||||
it('triggers `persist` hook', function(done) {
|
||||
TestModel.observe('persist', pushContextAndNext());
|
||||
|
||||
TestModel.updateAll(
|
||||
{ where: { name: existingInstance.name } },
|
||||
{ name: 'changed' },
|
||||
function(err, instance) {
|
||||
if (err) return done(err);
|
||||
|
||||
observedContexts.should.eql(aTestModelCtx({
|
||||
data: { name: 'changed' },
|
||||
where: { where: { name: existingInstance.name } }
|
||||
}));
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('applies updates from `persist` hook', function(done) {
|
||||
TestModel.observe('persist', pushContextAndNext(function(ctx){
|
||||
ctx.data.extra = 'hook data';
|
||||
}));
|
||||
|
||||
TestModel.updateAll(
|
||||
{ id: existingInstance.id },
|
||||
{ name: 'changed' },
|
||||
function(err) {
|
||||
if (err) return done(err);
|
||||
loadTestModel(existingInstance.id, function(err, instance) {
|
||||
instance.should.have.property('extra', 'hook data');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('triggers `after save` hook', function(done) {
|
||||
TestModel.observe('after save', pushContextAndNext());
|
||||
|
||||
|
|
Loading…
Reference in New Issue