Skip to content

Commit

Permalink
Expose upsertWithWhere method
Browse files Browse the repository at this point in the history
  • Loading branch information
sonalisam committed Sep 2, 2016
1 parent 387be29 commit aef6dca
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ module.exports = function(registry) {
return ACL.WRITE;
case 'updateOrCreate':
return ACL.WRITE;
case 'upsertWithWhere':
return ACL.WRITE;
case 'upsert':
return ACL.WRITE;
case 'exists':
Expand Down
37 changes: 37 additions & 0 deletions lib/persisted-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,28 @@ module.exports = function(registry) {
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,
* such that parameter `data.id` matches `id` of model instance; otherwise,
Expand Down Expand Up @@ -654,6 +676,21 @@ module.exports = function(registry) {

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', {
description: 'Check whether a model instance exists in the data source.',
accessType: 'READ',
Expand Down
8 changes: 8 additions & 0 deletions test/access-control.integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForUser);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForUser);
Expand Down Expand Up @@ -193,6 +197,10 @@ describe('access control - integration', function() {
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_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() {
return '/api/banks/' + this.bank.id;
}
Expand Down
2 changes: 2 additions & 0 deletions test/data-source.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('DataSource', function() {
assert.isFunc(Color, 'findOne');
assert.isFunc(Color, 'create');
assert.isFunc(Color, 'updateOrCreate');
assert.isFunc(Color, 'upsertWithWhere');
assert.isFunc(Color, 'upsert');
assert.isFunc(Color, 'findOrCreate');
assert.isFunc(Color, 'exists');
Expand Down Expand Up @@ -82,6 +83,7 @@ describe('DataSource', function() {
existsAndShared('_forDB', false);
existsAndShared('create', true);
existsAndShared('updateOrCreate', true);
existsAndShared('upsertWithWhere', true);
existsAndShared('upsert', true);
existsAndShared('findOrCreate', false);
existsAndShared('exists', true);
Expand Down
39 changes: 39 additions & 0 deletions test/model.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
it('Call the method using HTTP / REST', function(done) {
request(app)
Expand Down Expand Up @@ -515,6 +552,7 @@ describe.onServer('Remote Methods', function() {
describe('Model.checkAccessTypeForMethod(remoteMethod)', function() {
shouldReturn('create', ACL.WRITE);
shouldReturn('updateOrCreate', ACL.WRITE);
shouldReturn('upsertWithWhere', ACL.WRITE);
shouldReturn('upsert', ACL.WRITE);
shouldReturn('exists', ACL.READ);
shouldReturn('findById', ACL.READ);
Expand Down Expand Up @@ -634,6 +672,7 @@ describe.onServer('Remote Methods', function() {
// 'destroyAll', 'deleteAll', 'remove',
'create',
'upsert', 'updateOrCreate', 'patchOrCreate',
'upsertWithWhere', 'patchOrCreateWithWhere',
'exists',
'findById',
'replaceById',
Expand Down
10 changes: 10 additions & 0 deletions test/remoting.integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,15 @@ describe('remoting - integration', function() {
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() {
Expand All @@ -202,6 +211,7 @@ describe('With model.settings.replaceOnPUT false', function() {
'patchOrCreate(data:object):storeWithReplaceOnPUTfalse PUT /stores-updating',
'patchOrCreate(data:object):storeWithReplaceOnPUTfalse PATCH /stores-updating',
'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 HEAD /stores-updating/:id',
'findById(id:any,filter:object):storeWithReplaceOnPUTfalse GET /stores-updating/:id',
Expand Down
13 changes: 13 additions & 0 deletions test/replication.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
// make sure we bypass find+create and call the connector directly
SourceModel.dataSource.connector.findOrCreate =
Expand Down

0 comments on commit aef6dca

Please sign in to comment.