upsertWithWhere feature support in juggler DAO
This commit is contained in:
parent
552f50bdf1
commit
37541dd178
|
@ -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) {
|
Memory.prototype.findOrCreate = function(model, filter, data, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var nodes = self._findAllSkippingIncludes(model, filter);
|
var nodes = self._findAllSkippingIncludes(model, filter);
|
||||||
|
|
166
lib/dao.js
166
lib/dao.js
|
@ -630,7 +630,173 @@ DataAccessObject.upsert = function(data, options, cb) {
|
||||||
}
|
}
|
||||||
return cb.promise;
|
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;
|
* 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.
|
* otherwise, insert a new record.
|
||||||
|
|
|
@ -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) {
|
function seed(done) {
|
||||||
var beatles = [
|
var beatles = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -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) {
|
if (!getSchema().connector.replaceById) {
|
||||||
describe.skip('replaceAttributes/replaceById - not implemented', function() {});
|
describe.skip('replaceAttributes/replaceById - not implemented', function() {});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -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) {
|
function nextWithError(err) {
|
||||||
return function(context, next) {
|
return function(context, next) {
|
||||||
next(err);
|
next(err);
|
||||||
|
|
Loading…
Reference in New Issue