Merge pull request #2539 from mountain1234585/upsertWithWhere

Add upsertWithWhere
This commit is contained in:
Amirali Jafarian 2016-09-06 15:54:21 -04:00 committed by GitHub
commit a6f8ec672d
7 changed files with 111 additions and 0 deletions

View File

@ -364,6 +364,8 @@ module.exports = function(registry) {
return ACL.WRITE; return ACL.WRITE;
case 'updateOrCreate': case 'updateOrCreate':
return ACL.WRITE; return ACL.WRITE;
case 'upsertWithWhere':
return ACL.WRITE;
case 'upsert': case 'upsert':
return ACL.WRITE; return ACL.WRITE;
case 'exists': case 'exists':

View File

@ -118,6 +118,28 @@ module.exports = function(registry) {
throwNotAttached(this.modelName, 'upsert'); throwNotAttached(this.modelName, 'upsert');
}; };
/**
* 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.
*/
PersistedModel.upsertWithWhere =
PersistedModel.patchOrCreateWithWhere = function upsertWithWhere(where, data, callback) {
throwNotAttached(this.modelName, 'upsertWithWhere');
};
/** /**
* Replace or insert a model instance; replace existing record if one is found, * Replace or insert a model instance; replace existing record if one is found,
* such that parameter `data.id` matches `id` of model instance; otherwise, * such that parameter `data.id` matches `id` of model instance; otherwise,
@ -654,6 +676,21 @@ module.exports = function(registry) {
setRemoting(PersistedModel, 'replaceOrCreate', replaceOrCreateOptions); setRemoting(PersistedModel, 'replaceOrCreate', replaceOrCreateOptions);
setRemoting(PersistedModel, 'upsertWithWhere', {
aliases: ['patchOrCreateWithWhere'],
description: 'Update an existing model instance or insert a new one into ' +
'the data source based on the where criteria.',
accessType: 'WRITE',
accepts: [
{ arg: 'where', type: 'object', http: { source: 'query' },
description: 'Criteria to match model instances' },
{ arg: 'data', type: 'object', http: { source: 'body' },
description: 'An object of model property name/value pairs' },
],
returns: { arg: 'data', type: typeName, root: true },
http: { verb: 'post', path: '/upsertWithWhere' },
});
setRemoting(PersistedModel, 'exists', { setRemoting(PersistedModel, 'exists', {
description: 'Check whether a model instance exists in the data source.', description: 'Check whether a model instance exists in the data source.',
accessType: 'READ', accessType: 'READ',

View File

@ -122,6 +122,10 @@ describe('access control - integration', function() {
}); });
}); });
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/users/upsertWithWhere');
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/users/upsertWithWhere');
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/users/upsertWithWhere');
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForUser); lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForUser);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForUser); lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForUser);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForUser); lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForUser);
@ -193,6 +197,10 @@ describe('access control - integration', function() {
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForBank); lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForBank);
lt.it.shouldBeAllowedWhenCalledByUser(SPECIAL_USER, 'DELETE', urlForBank); lt.it.shouldBeAllowedWhenCalledByUser(SPECIAL_USER, 'DELETE', urlForBank);
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/banks/upsertWithWhere');
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/banks/upsertWithWhere');
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/banks/upsertWithWhere');
function urlForBank() { function urlForBank() {
return '/api/banks/' + this.bank.id; return '/api/banks/' + this.bank.id;
} }

View File

@ -22,6 +22,7 @@ describe('DataSource', function() {
assert.isFunc(Color, 'findOne'); assert.isFunc(Color, 'findOne');
assert.isFunc(Color, 'create'); assert.isFunc(Color, 'create');
assert.isFunc(Color, 'updateOrCreate'); assert.isFunc(Color, 'updateOrCreate');
assert.isFunc(Color, 'upsertWithWhere');
assert.isFunc(Color, 'upsert'); assert.isFunc(Color, 'upsert');
assert.isFunc(Color, 'findOrCreate'); assert.isFunc(Color, 'findOrCreate');
assert.isFunc(Color, 'exists'); assert.isFunc(Color, 'exists');
@ -82,6 +83,7 @@ describe('DataSource', function() {
existsAndShared('_forDB', false); existsAndShared('_forDB', false);
existsAndShared('create', true); existsAndShared('create', true);
existsAndShared('updateOrCreate', true); existsAndShared('updateOrCreate', true);
existsAndShared('upsertWithWhere', true);
existsAndShared('upsert', true); existsAndShared('upsert', true);
existsAndShared('findOrCreate', false); existsAndShared('findOrCreate', false);
existsAndShared('exists', true); existsAndShared('exists', true);

View File

@ -146,6 +146,43 @@ describe.onServer('Remote Methods', function() {
}); });
}); });
describe('Model.upsertWithWhere(where, data, callback)', function() {
it('Updates when a Model instance is retreived from data source', function(done) {
var taskEmitter = new TaskEmitter();
taskEmitter
.task(User, 'create', { first: 'jill', second: 'pill' })
.task(User, 'create', { first: 'bob', second: 'sob' })
.on('done', function() {
User.upsertWithWhere({ second: 'pill' }, { second: 'jones' }, function(err, user) {
if (err) return done(err);
var id = user.id;
User.findById(id, function(err, user) {
if (err) return done(err);
assert.equal(user.second, 'jones');
done();
});
});
});
});
it('Creates when no Model instance is retreived from data source', function(done) {
var taskEmitter = new TaskEmitter();
taskEmitter
.task(User, 'create', { first: 'simon', second: 'somers' })
.on('done', function() {
User.upsertWithWhere({ first: 'somers' }, { first: 'Simon' }, function(err, user) {
if (err) return done(err);
var id = user.id;
User.findById(id, function(err, user) {
if (err) return done(err);
assert.equal(user.first, 'Simon');
done();
});
});
});
});
});
describe('Example Remote Method', function() { describe('Example Remote Method', function() {
it('Call the method using HTTP / REST', function(done) { it('Call the method using HTTP / REST', function(done) {
request(app) request(app)
@ -515,6 +552,7 @@ describe.onServer('Remote Methods', function() {
describe('Model.checkAccessTypeForMethod(remoteMethod)', function() { describe('Model.checkAccessTypeForMethod(remoteMethod)', function() {
shouldReturn('create', ACL.WRITE); shouldReturn('create', ACL.WRITE);
shouldReturn('updateOrCreate', ACL.WRITE); shouldReturn('updateOrCreate', ACL.WRITE);
shouldReturn('upsertWithWhere', ACL.WRITE);
shouldReturn('upsert', ACL.WRITE); shouldReturn('upsert', ACL.WRITE);
shouldReturn('exists', ACL.READ); shouldReturn('exists', ACL.READ);
shouldReturn('findById', ACL.READ); shouldReturn('findById', ACL.READ);
@ -634,6 +672,7 @@ describe.onServer('Remote Methods', function() {
// 'destroyAll', 'deleteAll', 'remove', // 'destroyAll', 'deleteAll', 'remove',
'create', 'create',
'upsert', 'updateOrCreate', 'patchOrCreate', 'upsert', 'updateOrCreate', 'patchOrCreate',
'upsertWithWhere', 'patchOrCreateWithWhere',
'exists', 'exists',
'findById', 'findById',
'replaceById', 'replaceById',

View File

@ -183,6 +183,15 @@ describe('remoting - integration', function() {
expect(methods).to.include.members(expectedMethods); expect(methods).to.include.members(expectedMethods);
}); });
}); });
it('has upsertWithWhere remote method', function() {
var storeClass = findClass('store');
var methods = getFormattedMethodsExcludingRelations(storeClass.methods);
var expectedMethods = [
'upsertWithWhere(where:object,data:object):store POST /stores/upsertWithWhere',
];
expect(methods).to.include.members(expectedMethods);
});
}); });
describe('With model.settings.replaceOnPUT false', function() { describe('With model.settings.replaceOnPUT false', function() {
@ -202,6 +211,7 @@ describe('With model.settings.replaceOnPUT false', function() {
'patchOrCreate(data:object):storeWithReplaceOnPUTfalse PUT /stores-updating', 'patchOrCreate(data:object):storeWithReplaceOnPUTfalse PUT /stores-updating',
'patchOrCreate(data:object):storeWithReplaceOnPUTfalse PATCH /stores-updating', 'patchOrCreate(data:object):storeWithReplaceOnPUTfalse PATCH /stores-updating',
'replaceOrCreate(data:object):storeWithReplaceOnPUTfalse POST /stores-updating/replaceOrCreate', 'replaceOrCreate(data:object):storeWithReplaceOnPUTfalse POST /stores-updating/replaceOrCreate',
'upsertWithWhere(where:object,data:object):storeWithReplaceOnPUTfalse POST /stores-updating/upsertWithWhere',
'exists(id:any):boolean GET /stores-updating/:id/exists', 'exists(id:any):boolean GET /stores-updating/:id/exists',
'exists(id:any):boolean HEAD /stores-updating/:id', 'exists(id:any):boolean HEAD /stores-updating/:id',
'findById(id:any,filter:object):storeWithReplaceOnPUTfalse GET /stores-updating/:id', 'findById(id:any,filter:object):storeWithReplaceOnPUTfalse GET /stores-updating/:id',

View File

@ -1010,6 +1010,19 @@ describe('Replication / Change APIs', function() {
}); });
}); });
it('detects "upsertWithWhere"', function(done) {
givenReplicatedInstance(function(err, inst) {
if (err) return done(err);
SourceModel.upsertWithWhere(
{ name: inst.name },
{ name: 'updated' },
function(err) {
if (err) return done(err);
assertChangeRecordedForId(inst.id, done);
});
});
});
it('detects "findOrCreate"', function(done) { it('detects "findOrCreate"', function(done) {
// make sure we bypass find+create and call the connector directly // make sure we bypass find+create and call the connector directly
SourceModel.dataSource.connector.findOrCreate = SourceModel.dataSource.connector.findOrCreate =