upsertWithWhere feature support in juggler DAO

This commit is contained in:
Sonali Samantaray 2016-08-16 18:06:01 +05:30
parent 552f50bdf1
commit 37541dd178
5 changed files with 888 additions and 0 deletions

View File

@ -252,6 +252,36 @@ Memory.prototype.updateOrCreate = function(model, data, options, callback) {
});
};
Memory.prototype.patchOrCreateWithWhere =
Memory.prototype.upsertWithWhere = function(model, where, data, options, callback) {
var self = this;
var primaryKey = this.idName(model);
var filter = { where: where };
var nodes = self._findAllSkippingIncludes(model, filter);
if (nodes.length === 0) {
return self._createSync(model, data, function(err, id) {
if (err) return process.nextTick(function() { callback(err); });
self.saveToFile(id, function(err, id) {
self.setIdValue(model, data, id);
callback(err, self.fromDb(model, data), { isNewInstance: true });
});
});
}
if (nodes.length === 1) {
var primaryKeyValue = nodes[0][primaryKey];
self.updateAttributes(model, primaryKeyValue, data, options, function(err, data) {
callback(err, data, { isNewInstance: false });
});
} else {
process.nextTick(function() {
var error = new Error('There are multiple instances found.' +
'Upsert Operation will not be performed!');
error.statusCode = 400;
callback(error);
});
}
};
Memory.prototype.findOrCreate = function(model, filter, data, callback) {
var self = this;
var nodes = self._findAllSkippingIncludes(model, filter);

View File

@ -630,7 +630,173 @@ DataAccessObject.upsert = function(data, options, cb) {
}
return cb.promise;
};
/**
* Update or insert a model instance based on the search criteria.
* If there is a single instance retrieved, update the retrieved model.
* Creates a new model if no model instances were found.
* Returns an error if multiple instances are found.
* * @param {Object} [where] `where` filter, like
* ```
* { key: val, key2: {gt: 'val2'}, ...}
* ```
* <br/>see
* [Where filter](https://docs.strongloop.com/display/LB/Where+filter#Wherefilter-Whereclauseforothermethods).
* @param {Object} data The model instance data to insert.
* @callback {Function} callback Callback function called with `cb(err, obj)` signature.
* @param {Error} err Error object; see [Error object](http://docs.strongloop.com/display/LB/Error+object).
* @param {Object} model Updated model instance.
*/
DataAccessObject.patchOrCreateWithWhere =
DataAccessObject.upsertWithWhere = function(where, data, options, cb) {
var connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
if (connectionPromise) { return connectionPromise; }
if (cb === undefined) {
if (typeof options === 'function') {
// upsertWithWhere(where, data, cb)
cb = options;
options = {};
}
}
cb = cb || utils.createPromiseCallback();
options = options || {};
assert(typeof where === 'object', 'The where argument must be an object');
assert(typeof data === 'object', 'The data 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');
if (Object.keys(data).length === 0) {
var err = new Error('data object cannot be empty!');
err.statusCode = 400;
process.nextTick(function() { cb(err); });
return cb.promise;
}
var hookState = {};
var self = this;
var Model = this;
var connector = Model.getConnector();
var modelName = Model.modelName;
var query = { where: where };
var context = {
Model: Model,
query: query,
hookState: hookState,
options: options,
};
Model.notifyObserversOf('access', context, doUpsertWithWhere);
function doUpsertWithWhere(err, ctx) {
if (err) return cb(err);
ctx.data = data;
if (connector.upsertWithWhere) {
var context = {
Model: Model,
where: ctx.query.where,
data: ctx.data,
hookState: hookState,
options: options,
};
Model.notifyObserversOf('before save', context, function(err, ctx) {
if (err) return cb(err);
data = ctx.data;
var update = data;
var inst = data;
if (!(data instanceof Model)) {
inst = new Model(data, { applyDefaultValues: false });
}
update = inst.toObject(false);
Model.applyScope(query);
Model.applyProperties(update, inst);
Model = Model.lookupModel(update);
if (options.validate === false) {
return callConnector();
}
if (options.validate === undefined && Model.settings.automaticValidation === false) {
return callConnector();
}
inst.isValid(function(valid) {
if (!valid) return cb(new ValidationError(inst), inst);
callConnector();
}, update, options);
function callConnector() {
try {
ctx.where = removeUndefined(ctx.where);
ctx.where = Model._coerce(ctx.where);
update = removeUndefined(update);
update = Model._coerce(update);
} catch (err) {
return process.nextTick(function() {
cb(err);
});
}
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);
connector.upsertWithWhere(modelName, ctx.where, update, options, done);
});
}
function done(err, data, info) {
if (err) return cb(err);
var contxt = {
Model: Model,
data: data,
isNewInstance: info && info.isNewInstance,
hookState: ctx.hookState,
options: options,
};
Model.notifyObserversOf('loaded', contxt, function(err) {
if (err) return cb(err);
var obj;
if (contxt.data && !(contxt.data instanceof Model)) {
inst._initProperties(contxt.data, { persisted: true });
obj = inst;
} else {
obj = contxt.data;
}
var context = {
Model: Model,
instance: obj,
isNewInstance: info ? info.isNewInstance : undefined,
hookState: hookState,
options: options,
};
Model.notifyObserversOf('after save', context, function(err) {
cb(err, obj);
});
});
}
});
} else {
var opts = { notify: false };
if (ctx.options && ctx.options.transaction) {
opts.transaction = ctx.options.transaction;
}
self.find({ where: ctx.query.where }, opts, function(err, instances) {
if (err) return cb(err);
var modelsLength = instances.length;
if (modelsLength === 0) {
self.create(data, options, cb);
} else if (modelsLength === 1) {
var modelInst = instances[0];
modelInst.updateAttributes(data, options, cb);
} else {
process.nextTick(function() {
var error = new Error('There are multiple instances found.' +
'Upsert Operation will not be performed!');
error.statusCode = 400;
cb(error);
});
}
});
}
}
return cb.promise;
};
/**
* Replace or insert a model instance: replace exiting record if one is found, such that parameter `data.id` matches `id` of model instance;
* otherwise, insert a new record.

View File

@ -539,6 +539,69 @@ describe('crud-with-options', function() {
});
describe('upsertWithWhere', function() {
beforeEach(seed);
it('rejects upsertWithWhere (options,cb)', function(done) {
try {
User.upsertWithWhere({}, function(err) {
if (err) return done(err);
});
} catch (ex) {
ex.message.should.equal('The data argument must be an object');
done();
}
});
it('rejects upsertWithWhere (cb)', function(done) {
try {
User.upsertWithWhere(function(err) {
if (err) return done(err);
});
} catch (ex) {
ex.message.should.equal('The where argument must be an object');
done();
}
});
it('allows upsertWithWhere by accepting where,data and cb as arguments', function(done) {
User.upsertWithWhere({ name: 'John Lennon' }, { name: 'John Smith' }, function(err) {
if (err) return done(err);
User.find({ where: { name: 'John Lennon' }}, function(err, data) {
if (err) return done(err);
data.length.should.equal(0);
User.find({ where: { name: 'John Smith' }}, function(err, data) {
if (err) return done(err);
data.length.should.equal(1);
data[0].name.should.equal('John Smith');
data[0].email.should.equal('john@b3atl3s.co.uk');
data[0].role.should.equal('lead');
data[0].order.should.equal(2);
data[0].vip.should.equal(true);
done();
});
});
});
});
it('allows upsertWithWhere by accepting where, data, options, and cb as arguments', function(done) {
options = {};
User.upsertWithWhere({ name: 'John Lennon' }, { name: 'John Smith' }, options, function(err) {
if (err) return done(err);
User.find({ where: { name: 'John Smith' }}, function(err, data) {
if (err) return done(err);
data.length.should.equal(1);
data[0].name.should.equal('John Smith');
data[0].seq.should.equal(0);
data[0].email.should.equal('john@b3atl3s.co.uk');
data[0].role.should.equal('lead');
data[0].order.should.equal(2);
data[0].vip.should.equal(true);
done();
});
});
});
});
function seed(done) {
var beatles = [
{

View File

@ -1045,6 +1045,275 @@ describe('manipulation', function() {
});
}
describe('upsertWithWhere', function() {
var ds = getSchema();
var Person;
before('prepare "Person" model', function(done) {
Person = ds.define('Person', {
id: { type: Number, id: true },
name: { type: String },
city: { type: String },
});
ds.automigrate('Person', done);
});
it('has an alias "patchOrCreateWithWhere"', function() {
StubUser.upsertWithWhere.should.equal(StubUser.patchOrCreateWithWhere);
});
it('should preserve properties with dynamic setters on create', function(done) {
StubUser.upsertWithWhere({ password: 'foo' }, { password: 'foo' }, function(err, created) {
if (err) return done(err);
created.password.should.equal('foo-FOO');
StubUser.findById(created.id, function(err, found) {
if (err) return done(err);
found.password.should.equal('foo-FOO');
done();
});
});
});
it('should preserve properties with dynamic setters on update', function(done) {
StubUser.create({ password: 'foo' }, function(err, created) {
if (err) return done(err);
var data = { password: 'bar' };
StubUser.upsertWithWhere({ id: created.id }, data, function(err, updated) {
if (err) return done(err);
updated.password.should.equal('bar-BAR');
StubUser.findById(created.id, function(err, found) {
if (err) return done(err);
found.password.should.equal('bar-BAR');
done();
});
});
});
});
it('should preserve properties with "undefined" value', function(done) {
Person.create(
{ id: 10, name: 'Ritz', city: undefined },
function(err, instance) {
if (err) return done(err);
instance.toObject().should.have.properties({
id: 10,
name: 'Ritz',
city: undefined,
});
Person.upsertWithWhere({ id: 10 },
{ name: 'updated name' },
function(err, updated) {
if (err) return done(err);
var result = updated.toObject();
result.should.have.properties({
id: instance.id,
name: 'updated name',
});
should.equal(result.city, null);
done();
});
});
});
it('updates specific instances when PK is not an auto-generated id', function(done) {
Person.create([
{ name: 'nameA', city: 'cityA' },
{ name: 'nameB', city: 'cityB' },
], function(err, instance) {
if (err) return done(err);
Person.upsertWithWhere({ name: 'nameA' },
{ city: 'newCity' },
function(err, instance) {
if (err) return done(err);
var result = instance.toObject();
result.should.have.properties({
name: 'nameA',
city: 'newCity',
});
Person.find(function(err, persons) {
if (err) return done(err);
persons.should.have.length(3);
persons[1].name.should.equal('nameA');
persons[1].city.should.equal('newCity');
persons[2].name.should.equal('nameB');
persons[2].city.should.equal('cityB');
done();
});
});
});
});
it('should allow save() of the created instance', function(done) {
Person.upsertWithWhere({ id: 999 },
{ name: 'a-name' },
function(err, inst) {
if (err) return done(err);
inst.save(done);
});
});
it('works without options on create (promise variant)', function(done) {
var person = { id: 123, name: 'a', city: 'city a' };
Person.upsertWithWhere({ id: 123 }, person)
.then(function(p) {
should.exist(p);
p.should.be.instanceOf(Person);
p.id.should.be.equal(person.id);
p.should.not.have.property('_id');
p.name.should.equal(person.name);
p.city.should.equal(person.city);
return Person.findById(p.id)
.then(function(p) {
p.id.should.equal(person.id);
p.id.should.not.have.property('_id');
p.name.should.equal(person.name);
p.city.should.equal(person.city);
done();
});
})
.catch(done);
});
it('works with options on create (promise variant)', function(done) {
var person = { id: 234, name: 'b', city: 'city b' };
Person.upsertWithWhere({ id: 234 }, person, { validate: false })
.then(function(p) {
should.exist(p);
p.should.be.instanceOf(Person);
p.id.should.be.equal(person.id);
p.should.not.have.property('_id');
p.name.should.equal(person.name);
p.city.should.equal(person.city);
return Person.findById(p.id)
.then(function(p) {
p.id.should.equal(person.id);
p.id.should.not.have.property('_id');
p.name.should.equal(person.name);
p.city.should.equal(person.city);
done();
});
})
.catch(done);
});
it('works without options on update (promise variant)', function(done) {
var person = { id: 456, name: 'AAA', city: 'city AAA' };
Person.create(person)
.then(function(created) {
created = created.toObject();
delete created.city;
created.name = 'BBB';
return Person.upsertWithWhere({ id: 456 }, created)
.then(function(p) {
should.exist(p);
p.should.be.instanceOf(Person);
p.id.should.equal(created.id);
p.should.not.have.property('_id');
p.name.should.equal('BBB');
p.should.have.property('city', 'city AAA');
return Person.findById(created.id)
.then(function(p) {
p.should.not.have.property('_id');
p.name.should.equal('BBB');
p.city.should.equal('city AAA');
done();
});
});
})
.catch(done);
});
it('works with options on update (promise variant)', function(done) {
var person = { id: 789, name: 'CCC', city: 'city CCC' };
Person.create(person)
.then(function(created) {
created = created.toObject();
delete created.city;
created.name = 'Carlton';
return Person.upsertWithWhere({ id: 789 }, created, { validate: false })
.then(function(p) {
should.exist(p);
p.should.be.instanceOf(Person);
p.id.should.equal(created.id);
p.should.not.have.property('_id');
p.name.should.equal('Carlton');
p.should.have.property('city', 'city CCC');
return Person.findById(created.id)
.then(function(p) {
p.should.not.have.property('_id');
p.name.should.equal('Carlton');
p.city.should.equal('city CCC');
done();
});
});
})
.catch(done);
});
it('fails the upsertWithWhere operation when data object is empty', function(done) {
options = {};
Person.upsertWithWhere({ name: 'John Lennon' }, {}, options,
function(err) {
err.message.should.equal('data object cannot be empty!');
done();
});
});
it('creates a new record when no matching instance is found', function(done) {
Person.upsertWithWhere({ city: 'Florida' }, { name: 'Nick Carter', id: 1, city: 'Florida' },
function(err, created) {
if (err) return done(err);
Person.findById(1, function(err, data) {
if (err) return done(err);
data.id.should.equal(1);
data.name.should.equal('Nick Carter');
data.city.should.equal('Florida');
done();
});
});
});
it('fails the upsertWithWhere operation when multiple instances are ' +
'retrieved based on the filter criteria', function(done) {
Person.create([
{ id: '2', name: 'Howie', city: 'Florida' },
{ id: '3', name: 'Kevin', city: 'Florida' },
], function(err, instance) {
if (err) return done(err);
Person.upsertWithWhere({ city: 'Florida' }, {
id: '4', name: 'Brian',
}, function(err) {
err.message.should.equal('There are multiple instances found.' +
'Upsert Operation will not be performed!');
done();
});
});
});
it('updates the record when one matching instance is found ' +
'based on the filter criteria', function(done) {
Person.create([
{ id: '5', name: 'Howie', city: 'Kentucky' },
], function(err, instance) {
if (err) return done(err);
Person.upsertWithWhere({ city: 'Kentucky' }, {
name: 'Brian',
}, { validate: false }, function(err, instance) {
if (err) return done(err);
Person.findById(5, function(err, data) {
if (err) return done(err);
data.id.should.equal(5);
data.name.should.equal('Brian');
data.city.should.equal('Kentucky');
done();
});
});
});
});
});
if (!getSchema().connector.replaceById) {
describe.skip('replaceAttributes/replaceById - not implemented', function() {});
} else {

View File

@ -2946,6 +2946,366 @@ module.exports = function(dataSource, should, connectorCapabilities) {
});
describe('PersistedModel.upsertWithWhere', function() {
it('triggers hooks in the correct order on create', function(done) {
monitorHookExecution();
TestModel.upsertWithWhere({ extra: 'not-found' },
{ id: 'not-found', name: 'not found', extra: 'not-found' },
function(err, record, created) {
if (err) return done(err);
hookMonitor.names.should.eql([
'access',
'before save',
'persist',
'loaded',
'after save',
]);
TestModel.findById('not-found', function(err, data) {
if (err) return done(err);
data.name.should.equal('not found');
data.extra.should.equal('not-found');
done();
});
});
});
it('triggers hooks in the correct order on update', function(done) {
monitorHookExecution();
TestModel.upsertWithWhere({ id: existingInstance.id },
{ name: 'new name', extra: 'new extra' },
function(err, record, created) {
if (err) return done(err);
hookMonitor.names.should.eql([
'access',
'before save',
'persist',
'loaded',
'after save',
]);
TestModel.findById(existingInstance.id, function(err, data) {
if (err) return done(err);
data.name.should.equal('new name');
data.extra.should.equal('new extra');
done();
});
});
});
it('triggers `access` hook on create', function(done) {
TestModel.observe('access', ctxRecorder.recordAndNext());
TestModel.upsertWithWhere({ extra: 'not-found' },
{ id: 'not-found', name: 'not found' },
function(err, instance) {
if (err) return done(err);
ctxRecorder.records.should.eql(aCtxForModel(TestModel, { query: {
where: { extra: 'not-found' },
}}));
done();
});
});
it('triggers `access` hook on update', function(done) {
TestModel.observe('access', ctxRecorder.recordAndNext());
TestModel.upsertWithWhere({ id: existingInstance.id },
{ name: 'new name', extra: 'new extra' },
function(err, instance) {
if (err) return done(err);
ctxRecorder.records.should.eql(aCtxForModel(TestModel, { query: {
where: { id: existingInstance.id },
}}));
done();
});
});
it('triggers hooks only once', function(done) {
monitorHookExecution(['access', 'before save']);
TestModel.observe('access', function(ctx, next) {
ctx.query = { where: { id: { neq: existingInstance.id }}};
next();
});
TestModel.upsertWithWhere({ id: existingInstance.id },
{ id: 'ignored', name: 'new name' },
function(err, instance) {
if (err) return done(err);
hookMonitor.names.should.eql(['access', 'before save']);
done();
});
});
it('applies updates from `access` hook when found', function(done) {
TestModel.observe('access', function(ctx, next) {
ctx.query = { where: { id: { neq: existingInstance.id }}};
next();
});
TestModel.upsertWithWhere({ id: existingInstance.id },
{ name: 'new name' },
function(err, instance) {
if (err) return done(err);
findTestModels({ fields: ['id', 'name'] }, function(err, list) {
if (err) return done(err);
(list || []).map(toObject).should.eql([
{ id: existingInstance.id, name: existingInstance.name, extra: undefined },
{ id: instance.id, name: 'new name', extra: undefined },
]);
done();
});
});
});
it('applies updates from `access` hook when not found', function(done) {
TestModel.observe('access', function(ctx, next) {
ctx.query = { where: { id: 'not-found' }};
next();
});
TestModel.upsertWithWhere({ id: existingInstance.id },
{ name: 'new name' },
function(err, instance) {
if (err) return done(err);
findTestModels({ fields: ['id', 'name'] }, function(err, list) {
if (err) return done(err);
(list || []).map(toObject).should.eql([
{ id: existingInstance.id, name: existingInstance.name, extra: undefined },
{ id: list[1].id, name: 'second', extra: undefined },
{ id: instance.id, name: 'new name', extra: undefined },
]);
done();
});
});
});
it('triggers `before save` hook on update', function(done) {
TestModel.observe('before save', ctxRecorder.recordAndNext());
TestModel.upsertWithWhere({ id: existingInstance.id },
{ id: existingInstance.id, name: 'updated name' },
function(err, instance) {
if (err) return done(err);
var expectedContext = aCtxForModel(TestModel, {
where: { id: existingInstance.id },
data: {
id: existingInstance.id,
name: 'updated name',
},
});
if (!dataSource.connector.upsertWithWhere) {
expectedContext.currentInstance = existingInstance;
}
ctxRecorder.records.should.eql(expectedContext);
done();
});
});
it('triggers `before save` hook on create', function(done) {
TestModel.observe('before save', ctxRecorder.recordAndNext());
TestModel.upsertWithWhere({ id: 'new-id' },
{ id: 'new-id', name: 'a name' },
function(err, instance) {
if (err) return done(err);
var expectedContext = aCtxForModel(TestModel, {
where: { id: 'new-id' },
data: { id: 'new-id', name: 'a name' },
});
if (!dataSource.connector.upsertWithWhere) {
ctxRecorder.records.should.eql(expectedContext.isNewInstance = true);
}
ctxRecorder.records.should.eql(expectedContext);
done();
});
});
it('applies updates from `before save` hook on update', function(done) {
TestModel.observe('before save', function(ctx, next) {
ctx.data.name = 'hooked';
next();
});
TestModel.upsertWithWhere({ id: existingInstance.id },
{ name: 'updated name' },
function(err, instance) {
if (err) return done(err);
instance.name.should.equal('hooked');
done();
});
});
it('applies updates from `before save` hook on create', function(done) {
TestModel.observe('before save', function(ctx, next) {
if (ctx.instance) {
ctx.instance.name = 'hooked';
} else {
ctx.data.name = 'hooked';
}
next();
});
TestModel.upsertWithWhere({ id: 'new-id' },
{ id: 'new-id', name: 'new name' },
function(err, instance) {
if (err) return done(err);
instance.name.should.equal('hooked');
done();
});
});
it('validates model after `before save` hook on create', function(done) {
TestModel.observe('before save', invalidateTestModel());
TestModel.upsertWithWhere({ id: 'new-id' },
{ id: 'new-id', name: 'new name' },
function(err, instance) {
(err || {}).should.be.instanceOf(ValidationError);
(err.details.codes || {}).should.eql({ name: ['presence'] });
done();
});
});
it('validates model after `before save` hook on update', function(done) {
TestModel.observe('before save', invalidateTestModel());
TestModel.upsertWithWhere({ id: existingInstance.id },
{ id: existingInstance.id, name: 'updated name' },
function(err, instance) {
(err || {}).should.be.instanceOf(ValidationError);
(err.details.codes || {}).should.eql({ name: ['presence'] });
done();
});
});
it('triggers `persist` hook on create', function(done) {
TestModel.observe('persist', ctxRecorder.recordAndNext());
TestModel.upsertWithWhere({ id: 'new-id' },
{ id: 'new-id', name: 'a name' },
function(err, instance) {
if (err) return done(err);
ctxRecorder.records.should.eql(aCtxForModel(TestModel, {
where: { id: 'new-id' },
data: { id: 'new-id', name: 'a name' },
currentInstance: {
id: 'new-id',
name: 'a name',
extra: undefined,
},
}));
done();
});
});
it('triggers persist hook on update', function(done) {
TestModel.observe('persist', ctxRecorder.recordAndNext());
TestModel.upsertWithWhere({ id: existingInstance.id },
{ id: existingInstance.id, name: 'updated name' },
function(err, instance) {
if (err) return done(err);
var expectedContext = aCtxForModel(TestModel, {
where: { id: existingInstance.id },
data: {
id: existingInstance.id,
name: 'updated name',
},
currentInstance: {
id: existingInstance.id,
name: 'updated name',
extra: undefined,
},
});
if (!dataSource.connector.upsertWithWhere) {
expectedContext.isNewInstance = false;
}
ctxRecorder.records.should.eql(expectedContext);
done();
});
});
it('triggers `loaded` hook on create', function(done) {
TestModel.observe('loaded', ctxRecorder.recordAndNext());
TestModel.upsertWithWhere({ id: 'new-id' },
{ id: 'new-id', name: 'a name' },
function(err, instance) {
if (err) return done(err);
ctxRecorder.records.should.eql(aCtxForModel(TestModel, {
data: { id: 'new-id', name: 'a name' },
isNewInstance: true,
}));
done();
});
});
it('triggers `loaded` hook on update', function(done) {
TestModel.observe('loaded', ctxRecorder.recordAndNext());
TestModel.upsertWithWhere({ id: existingInstance.id },
{ id: existingInstance.id, name: 'updated name' },
function(err, instance) {
if (err) return done(err);
ctxRecorder.records.should.eql(aCtxForModel(TestModel, {
data: {
id: existingInstance.id,
name: 'updated name',
},
isNewInstance: false,
}));
done();
});
});
it('emits error when `loaded` hook fails', function(done) {
TestModel.observe('loaded', nextWithError(expectedError));
TestModel.upsertWithWhere({ id: 'new-id' },
{ id: 'new-id', name: 'a name' },
function(err, instance) {
[err].should.eql([expectedError]);
done();
});
});
it('triggers `after save` hook on update', function(done) {
TestModel.observe('after save', ctxRecorder.recordAndNext());
TestModel.upsertWithWhere({ id: existingInstance.id },
{ id: existingInstance.id, name: 'updated name' },
function(err, instance) {
if (err) return done(err);
ctxRecorder.records.should.eql(aCtxForModel(TestModel, {
instance: {
id: existingInstance.id,
name: 'updated name',
extra: undefined,
},
isNewInstance: false,
}));
done();
});
});
it('triggers `after save` hook on create', function(done) {
TestModel.observe('after save', ctxRecorder.recordAndNext());
TestModel.upsertWithWhere({ id: 'new-id' },
{ id: 'new-id', name: 'a name' }, function(err, instance) {
if (err) return done(err);
ctxRecorder.records.should.eql(aCtxForModel(TestModel, {
instance: {
id: instance.id,
name: 'a name',
extra: undefined,
},
isNewInstance: true,
}));
done();
});
});
});
function nextWithError(err) {
return function(context, next) {
next(err);