Comparing version
Baucis Change Log | ||
================= | ||
v0.10.0 | ||
------- | ||
Send `409 Conflict` when there are document version conflicts. This is useful for optimistic locking. | ||
Mongoose only updates `__v` for certain array operations by default. To update for every save (optimistic locking), add this Mongoose middleware to the schema: | ||
schema.pre('save', funciton (next) { | ||
this.increment(); | ||
next(); | ||
}); | ||
By default, document version is only checked for conflict when the versionKey is sent with the PUT request, otherwise no version checking is done. To force version checking for all PUT requests: | ||
var controller = baucis.rest({ | ||
singular: 'tea', | ||
'always check version': true | ||
}); | ||
v0.9.4 | ||
@@ -5,0 +23,0 @@ ------ |
@@ -59,2 +59,5 @@ // __Dependencies__ | ||
var update = extend(request.body); | ||
var versionKey = request.baucis.controller.get('schema').get('versionKey'); | ||
var alwaysCheckVersion = request.baucis.controller.get('always check version'); | ||
var version = update[versionKey]; | ||
var done = function (error, saved) { | ||
@@ -67,2 +70,3 @@ if (error) return next(error); | ||
if (operator && validOperators.indexOf(operator) === -1) return next(new Error('Unsupported update operator: ' + operator)); | ||
if (alwaysCheckVersion && (version !== 0) && !version) return response.send(409); | ||
@@ -73,2 +77,13 @@ request.baucis.query.exec(function (error, doc) { | ||
var previousVersion = doc[versionKey]; | ||
if (alwaysCheckVersion && !doc.isSelected(versionKey)) { | ||
next(new Error('version key "'+ versionKey + '" was not selected.')); | ||
return; | ||
} | ||
if (previousVersion && (version || version === 0) && version < previousVersion) { | ||
response.send(409); | ||
return; | ||
} | ||
if (!operator) { | ||
@@ -75,0 +90,0 @@ doc.set(update); |
@@ -5,3 +5,3 @@ { | ||
"homepage": "https://github.com/wprl/baucis", | ||
"version": "0.9.4", | ||
"version": "0.10.0", | ||
"main": "index.js", | ||
@@ -8,0 +8,0 @@ "scripts": { |
@@ -1,3 +0,3 @@ | ||
baucis v0.9.4 | ||
============= | ||
baucis v0.10.0 | ||
============== | ||
@@ -230,2 +230,3 @@ Baucis is Express middleware that creates configurable REST APIs using Mongoose schemata. | ||
| head, get, post, put, del | May be set to false to disable those HTTP verbs completely for the controller | | ||
| always check version | Force all PUTs to send the document version (__v). By default, if a version is not sent with an update no version checking is performed. | | ||
@@ -232,0 +233,0 @@ An example of embedding a controller within another controller |
@@ -248,3 +248,3 @@ var expect = require('expect.js'); | ||
url: 'http://localhost:8012/api/v1/stores/123/tools/' + id, | ||
json: { name: 'Screwdriver' } | ||
json: { name: 'Screwdriver', __v: body[0].__v + 10 } | ||
}; | ||
@@ -342,3 +342,3 @@ request.put(options, function (error, response, body) { | ||
url: 'http://localhost:8012/api/v1/stores/Westlake', | ||
json: { mercoledi: false } | ||
json: { mercoledi: false, __v: 0 } | ||
}; | ||
@@ -482,3 +482,3 @@ request.put(options, function (error, response, body) { | ||
json: true, | ||
body: { molds: 'penicillium roqueforti' } | ||
body: { molds: 'penicillium roqueforti', __v: 0 } | ||
}; | ||
@@ -532,3 +532,3 @@ request.put(options, function (error, response, body) { | ||
json: true, | ||
body: { molds: 'penicillium roqueforti' } | ||
body: { molds: 'penicillium roqueforti', __v: 0 } | ||
}; | ||
@@ -593,3 +593,3 @@ request.put(options, function (error, response, body) { | ||
json: true, | ||
body: { molds: 'penicillium roqueforti' } | ||
body: { molds: 'penicillium roqueforti', __v: 0 } | ||
}; | ||
@@ -744,2 +744,95 @@ request.put(options, function (error, response, body) { | ||
it('should send "409 Conflict" if there is a version conflict', function (done) { | ||
var options = { | ||
url: 'http://localhost:8012/api/v1/stores/Westlake', | ||
json: true, | ||
body: { __v: 10 } | ||
}; | ||
request.put(options, function (error, response, body) { | ||
if (error) return done(error); | ||
expect(response).to.have.property('statusCode', 200); | ||
var options = { | ||
url: 'http://localhost:8012/api/v1/stores/Westlake', | ||
json: true, | ||
body: { __v: 0 } | ||
}; | ||
request.put(options, function (error, response, body) { | ||
if (error) return done(error); | ||
expect(response).to.have.property('statusCode', 409); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('should not send "409 Conflict" if there is no version conflict (equal)', function (done) { | ||
var options = { | ||
url: 'http://localhost:8012/api/v1/cheeses/Camembert', | ||
json: true | ||
}; | ||
request.get(options, function (error, response, body) { | ||
if (error) return done(error); | ||
expect(response).to.have.property('statusCode', 200); | ||
var options = { | ||
url: 'http://localhost:8012/api/v1/cheeses/Camembert', | ||
json: true, | ||
body: { __v: body.__v } | ||
}; | ||
request.put(options, function (error, response, body) { | ||
if (error) return done(error); | ||
expect(response).to.have.property('statusCode', 200); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('should not send "409 Conflict" if there is no version conflict (greater than)', function (done) { | ||
var options = { | ||
url: 'http://localhost:8012/api/v1/cheeses/Camembert', | ||
json: true | ||
}; | ||
request.get(options, function (error, response, body) { | ||
if (error) return done(error); | ||
expect(response).to.have.property('statusCode', 200); | ||
var options = { | ||
url: 'http://localhost:8012/api/v1/cheeses/Camembert', | ||
json: true, | ||
body: { __v: body.__v + 10 } | ||
}; | ||
request.put(options, function (error, response, body) { | ||
if (error) return done(error); | ||
expect(response).to.have.property('statusCode', 200); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('should send "409 Conflict" if alwaysCheckVersion is enabled and no version is sent', function (done) { | ||
var options = { | ||
url: 'http://localhost:8012/api/v1/stores/Westlake', | ||
json: true, | ||
body: { } | ||
}; | ||
request.put(options, function (error, response, body) { | ||
if (error) return done(error); | ||
expect(response).to.have.property('statusCode', 409); | ||
done(); | ||
}); | ||
}); | ||
it('should cause an error if alwaysCheckVersion is enabled and no version is selected', function (done) { | ||
var options = { | ||
url: 'http://localhost:8012/api/v1/stores/Westlake', | ||
json: true, | ||
qs: { select: '-__v' }, | ||
body: { __v: 1000 } | ||
}; | ||
request.put(options, function (error, response, body) { | ||
if (error) return done(error); | ||
expect(response).to.have.property('statusCode', 500); | ||
done(); | ||
}); | ||
}); | ||
}); |
@@ -22,2 +22,7 @@ var mongoose = require('mongoose'); | ||
Stores.on('save', function (next) { | ||
this.increment(); | ||
next(); | ||
}); | ||
var Tools = new Schema({ | ||
@@ -39,2 +44,7 @@ name: { type: String, required: true }, | ||
Cheese.on('save', function (next) { | ||
this.increment(); | ||
next(); | ||
}); | ||
var Beans = new Schema({ koji: Boolean }); | ||
@@ -72,3 +82,4 @@ var Deans = new Schema({ room: { type: Number, unique: true } }); | ||
findBy: 'name', | ||
select: '-mercoledi' | ||
select: '-mercoledi', | ||
'always check version': true | ||
}); | ||
@@ -104,2 +115,3 @@ | ||
findBy: 'name', | ||
// 'always check version': true, | ||
'allow $push': 'molds arbitrary arbitrary.$.llama', | ||
@@ -106,0 +118,0 @@ 'allow $set': 'molds arbitrary.$.champagne', |
618702
0.78%3694
3.04%261
0.38%