Skip to content

Commit a6f8ec6

Browse files
authored
Merge pull request #2539 from mountain1234585/upsertWithWhere
Add upsertWithWhere
2 parents d78d4c9 + aef6dca commit a6f8ec6

File tree

7 files changed

+111
-0
lines changed

7 files changed

+111
-0
lines changed

lib/model.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,8 @@ module.exports = function(registry) {
364364
return ACL.WRITE;
365365
case 'updateOrCreate':
366366
return ACL.WRITE;
367+
case 'upsertWithWhere':
368+
return ACL.WRITE;
367369
case 'upsert':
368370
return ACL.WRITE;
369371
case 'exists':

lib/persisted-model.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,28 @@ module.exports = function(registry) {
118118
throwNotAttached(this.modelName, 'upsert');
119119
};
120120

121+
/**
122+
* Update or insert a model instance based on the search criteria.
123+
* If there is a single instance retrieved, update the retrieved model.
124+
* Creates a new model if no model instances were found.
125+
* Returns an error if multiple instances are found.
126+
* * @param {Object} [where] `where` filter, like
127+
* ```
128+
* { key: val, key2: {gt: 'val2'}, ...}
129+
* ```
130+
* <br/>see
131+
* [Where filter](https://docs.strongloop.com/display/LB/Where+filter#Wherefilter-Whereclauseforothermethods).
132+
* @param {Object} data The model instance data to insert.
133+
* @callback {Function} callback Callback function called with `cb(err, obj)` signature.
134+
* @param {Error} err Error object; see [Error object](http://docs.strongloop.com/display/LB/Error+object).
135+
* @param {Object} model Updated model instance.
136+
*/
137+
138+
PersistedModel.upsertWithWhere =
139+
PersistedModel.patchOrCreateWithWhere = function upsertWithWhere(where, data, callback) {
140+
throwNotAttached(this.modelName, 'upsertWithWhere');
141+
};
142+
121143
/**
122144
* Replace or insert a model instance; replace existing record if one is found,
123145
* such that parameter `data.id` matches `id` of model instance; otherwise,
@@ -654,6 +676,21 @@ module.exports = function(registry) {
654676

655677
setRemoting(PersistedModel, 'replaceOrCreate', replaceOrCreateOptions);
656678

679+
setRemoting(PersistedModel, 'upsertWithWhere', {
680+
aliases: ['patchOrCreateWithWhere'],
681+
description: 'Update an existing model instance or insert a new one into ' +
682+
'the data source based on the where criteria.',
683+
accessType: 'WRITE',
684+
accepts: [
685+
{ arg: 'where', type: 'object', http: { source: 'query' },
686+
description: 'Criteria to match model instances' },
687+
{ arg: 'data', type: 'object', http: { source: 'body' },
688+
description: 'An object of model property name/value pairs' },
689+
],
690+
returns: { arg: 'data', type: typeName, root: true },
691+
http: { verb: 'post', path: '/upsertWithWhere' },
692+
});
693+
657694
setRemoting(PersistedModel, 'exists', {
658695
description: 'Check whether a model instance exists in the data source.',
659696
accessType: 'READ',

test/access-control.integration.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ describe('access control - integration', function() {
122122
});
123123
});
124124

125+
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/users/upsertWithWhere');
126+
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/users/upsertWithWhere');
127+
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/users/upsertWithWhere');
128+
125129
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForUser);
126130
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForUser);
127131
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForUser);
@@ -193,6 +197,10 @@ describe('access control - integration', function() {
193197
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForBank);
194198
lt.it.shouldBeAllowedWhenCalledByUser(SPECIAL_USER, 'DELETE', urlForBank);
195199

200+
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/banks/upsertWithWhere');
201+
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/banks/upsertWithWhere');
202+
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/banks/upsertWithWhere');
203+
196204
function urlForBank() {
197205
return '/api/banks/' + this.bank.id;
198206
}

test/data-source.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ describe('DataSource', function() {
2222
assert.isFunc(Color, 'findOne');
2323
assert.isFunc(Color, 'create');
2424
assert.isFunc(Color, 'updateOrCreate');
25+
assert.isFunc(Color, 'upsertWithWhere');
2526
assert.isFunc(Color, 'upsert');
2627
assert.isFunc(Color, 'findOrCreate');
2728
assert.isFunc(Color, 'exists');
@@ -82,6 +83,7 @@ describe('DataSource', function() {
8283
existsAndShared('_forDB', false);
8384
existsAndShared('create', true);
8485
existsAndShared('updateOrCreate', true);
86+
existsAndShared('upsertWithWhere', true);
8587
existsAndShared('upsert', true);
8688
existsAndShared('findOrCreate', false);
8789
existsAndShared('exists', true);

test/model.test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,43 @@ describe.onServer('Remote Methods', function() {
146146
});
147147
});
148148

149+
describe('Model.upsertWithWhere(where, data, callback)', function() {
150+
it('Updates when a Model instance is retreived from data source', function(done) {
151+
var taskEmitter = new TaskEmitter();
152+
taskEmitter
153+
.task(User, 'create', { first: 'jill', second: 'pill' })
154+
.task(User, 'create', { first: 'bob', second: 'sob' })
155+
.on('done', function() {
156+
User.upsertWithWhere({ second: 'pill' }, { second: 'jones' }, function(err, user) {
157+
if (err) return done(err);
158+
var id = user.id;
159+
User.findById(id, function(err, user) {
160+
if (err) return done(err);
161+
assert.equal(user.second, 'jones');
162+
done();
163+
});
164+
});
165+
});
166+
});
167+
168+
it('Creates when no Model instance is retreived from data source', function(done) {
169+
var taskEmitter = new TaskEmitter();
170+
taskEmitter
171+
.task(User, 'create', { first: 'simon', second: 'somers' })
172+
.on('done', function() {
173+
User.upsertWithWhere({ first: 'somers' }, { first: 'Simon' }, function(err, user) {
174+
if (err) return done(err);
175+
var id = user.id;
176+
User.findById(id, function(err, user) {
177+
if (err) return done(err);
178+
assert.equal(user.first, 'Simon');
179+
done();
180+
});
181+
});
182+
});
183+
});
184+
});
185+
149186
describe('Example Remote Method', function() {
150187
it('Call the method using HTTP / REST', function(done) {
151188
request(app)
@@ -515,6 +552,7 @@ describe.onServer('Remote Methods', function() {
515552
describe('Model.checkAccessTypeForMethod(remoteMethod)', function() {
516553
shouldReturn('create', ACL.WRITE);
517554
shouldReturn('updateOrCreate', ACL.WRITE);
555+
shouldReturn('upsertWithWhere', ACL.WRITE);
518556
shouldReturn('upsert', ACL.WRITE);
519557
shouldReturn('exists', ACL.READ);
520558
shouldReturn('findById', ACL.READ);
@@ -634,6 +672,7 @@ describe.onServer('Remote Methods', function() {
634672
// 'destroyAll', 'deleteAll', 'remove',
635673
'create',
636674
'upsert', 'updateOrCreate', 'patchOrCreate',
675+
'upsertWithWhere', 'patchOrCreateWithWhere',
637676
'exists',
638677
'findById',
639678
'replaceById',

test/remoting.integration.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,15 @@ describe('remoting - integration', function() {
183183
expect(methods).to.include.members(expectedMethods);
184184
});
185185
});
186+
187+
it('has upsertWithWhere remote method', function() {
188+
var storeClass = findClass('store');
189+
var methods = getFormattedMethodsExcludingRelations(storeClass.methods);
190+
var expectedMethods = [
191+
'upsertWithWhere(where:object,data:object):store POST /stores/upsertWithWhere',
192+
];
193+
expect(methods).to.include.members(expectedMethods);
194+
});
186195
});
187196

188197
describe('With model.settings.replaceOnPUT false', function() {
@@ -202,6 +211,7 @@ describe('With model.settings.replaceOnPUT false', function() {
202211
'patchOrCreate(data:object):storeWithReplaceOnPUTfalse PUT /stores-updating',
203212
'patchOrCreate(data:object):storeWithReplaceOnPUTfalse PATCH /stores-updating',
204213
'replaceOrCreate(data:object):storeWithReplaceOnPUTfalse POST /stores-updating/replaceOrCreate',
214+
'upsertWithWhere(where:object,data:object):storeWithReplaceOnPUTfalse POST /stores-updating/upsertWithWhere',
205215
'exists(id:any):boolean GET /stores-updating/:id/exists',
206216
'exists(id:any):boolean HEAD /stores-updating/:id',
207217
'findById(id:any,filter:object):storeWithReplaceOnPUTfalse GET /stores-updating/:id',

test/replication.test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,19 @@ describe('Replication / Change APIs', function() {
10101010
});
10111011
});
10121012

1013+
it('detects "upsertWithWhere"', function(done) {
1014+
givenReplicatedInstance(function(err, inst) {
1015+
if (err) return done(err);
1016+
SourceModel.upsertWithWhere(
1017+
{ name: inst.name },
1018+
{ name: 'updated' },
1019+
function(err) {
1020+
if (err) return done(err);
1021+
assertChangeRecordedForId(inst.id, done);
1022+
});
1023+
});
1024+
});
1025+
10131026
it('detects "findOrCreate"', function(done) {
10141027
// make sure we bypass find+create and call the connector directly
10151028
SourceModel.dataSource.connector.findOrCreate =

0 commit comments

Comments
 (0)