Skip to content

Commit d09a75b

Browse files
author
Tom Kirkpatrick
committed
fix; retain existing values on update
1 parent d4380b8 commit d09a75b

File tree

2 files changed

+50
-19
lines changed

2 files changed

+50
-19
lines changed

lib/read-only.js

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,27 @@
22

33
const debug = require('debug')('loopback:mixin:readonly')
44

5+
function deletePropertiesFrom(properties, data) {
6+
Object.keys(properties).forEach(key => {
7+
debug('The \'%s\' property is read only, removing incoming data', key)
8+
delete data[key]
9+
})
10+
}
11+
12+
function replacePropertiesWithValuesFrom(properties, from, to) {
13+
Object.keys(properties).forEach(key => {
14+
const value = from[key]
15+
16+
debug('The \'%s\' property is read only, replacing incoming data with existing value: %o', key, value)
17+
to[key] = value
18+
})
19+
}
20+
521
module.exports = Model => {
622
debug('ReadOnly mixin for Model %s', Model.modelName)
723

824
Model.on('attached', () => {
9-
Model.stripReadOnlyProperties = (modelName, ctx, next) => {
25+
Model.stripReadOnlyProperties = (modelName, ctx, modelInstance, next) => {
1026
debug('stripReadOnlyProperties for model %s (via remote method %o)', modelName, ctx.methodString)
1127
const { body } = ctx.req
1228

@@ -17,14 +33,29 @@ module.exports = Model => {
1733
const AffectedModel = Model.app.loopback.getModel(modelName)
1834
const options = AffectedModel.settings.mixins.ReadOnly
1935
const properties = (Object.keys(options).length) ? options : null
36+
const instanceId = ctx.args[AffectedModel.getIdName()]
2037

2138
if (properties) {
2239
debug('Found read only properties for model %s: %o', modelName, properties)
23-
Object.keys(properties).forEach(key => {
24-
debug('The \'%s\' property is read only, removing incoming data', key)
25-
delete body[key]
26-
})
40+
41+
// Handle the case for updating an existing instance.
42+
if (instanceId) {
43+
return AffectedModel.findById(instanceId)
44+
.then(instance => {
45+
if (instance) {
46+
replacePropertiesWithValuesFrom(properties, instance, body)
47+
}
48+
else {
49+
deletePropertiesFrom(properties, body)
50+
}
51+
return next()
52+
})
53+
}
54+
55+
// Handle the case creating a new instance.
56+
deletePropertiesFrom(properties, body)
2757
return next()
58+
2859
}
2960
const err = new Error(`Unable to update: ${modelName} is read only.`)
3061

@@ -34,31 +65,31 @@ module.exports = Model => {
3465

3566
// Handle native model methods.
3667
Model.beforeRemote('create', (ctx, modelInstance, next) => {
37-
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
68+
Model.stripReadOnlyProperties(Model.modelName, ctx, modelInstance, next)
3869
})
3970
Model.beforeRemote('upsert', (ctx, modelInstance, next) => {
40-
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
71+
Model.stripReadOnlyProperties(Model.modelName, ctx, modelInstance, next)
4172
})
4273
Model.beforeRemote('replaceOrCreate', (ctx, modelInstance, next) => {
43-
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
74+
Model.stripReadOnlyProperties(Model.modelName, ctx, modelInstance, next)
4475
})
4576
Model.beforeRemote('patchOrCreate', (ctx, modelInstance, next) => {
46-
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
77+
Model.stripReadOnlyProperties(Model.modelName, ctx, modelInstance, next)
4778
})
4879
Model.beforeRemote('prototype.updateAttributes', (ctx, modelInstance, next) => {
49-
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
80+
Model.stripReadOnlyProperties(Model.modelName, ctx, modelInstance, next)
5081
})
5182
Model.beforeRemote('prototype.patchAttributes', (ctx, modelInstance, next) => {
52-
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
83+
Model.stripReadOnlyProperties(Model.modelName, ctx, modelInstance, next)
5384
})
5485
Model.beforeRemote('updateAll', (ctx, modelInstance, next) => {
55-
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
86+
Model.stripReadOnlyProperties(Model.modelName, ctx, modelInstance, next)
5687
})
5788
Model.beforeRemote('upsertWithWhere', (ctx, modelInstance, next) => {
58-
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
89+
Model.stripReadOnlyProperties(Model.modelName, ctx, modelInstance, next)
5990
})
6091
Model.beforeRemote('replaceById', (ctx, modelInstance, next) => {
61-
Model.stripReadOnlyProperties(Model.modelName, ctx, next)
92+
Model.stripReadOnlyProperties(Model.modelName, ctx, modelInstance, next)
6293
})
6394

6495
// Handle updates via relationship.
@@ -71,7 +102,7 @@ module.exports = Model => {
71102

72103
Model.beforeRemote(`prototype.__updateById__${relationName}`, (ctx, modelInstance, next) => {
73104
if (typeof AffectedModel.stripReadOnlyProperties === 'function') {
74-
return AffectedModel.stripReadOnlyProperties(modelName, ctx, next)
105+
return AffectedModel.stripReadOnlyProperties(modelName, ctx, modelInstance, next)
75106
}
76107
return next()
77108
})

test/test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ describe('loopback datasource readonly property (mixin sources.js)', function()
8888
.expect(200)
8989
.then(res => {
9090
expect(res.body.name).to.equal('Tom (edited)')
91-
expect(res.body.status).to.be.undefined()
92-
expect(res.body.role).to.be.undefined()
91+
expect(res.body.status).to.equal('disabled')
92+
expect(res.body.role).to.equal('user')
9393
})
9494
})
9595
})
@@ -171,11 +171,11 @@ describe('loopback datasource readonly property (mixin sources.js)', function()
171171
describe('replaceById', function() {
172172
lt.beforeEach.givenModel('Product', { name: 'book 1', type: 'book', status: 'pending' }, 'product')
173173
it('should not change readonly properties', function() {
174-
return json('post', `/api/products/${this.product.id}/replace`)
174+
return json('put', `/api/products/${this.product.id}`)
175175
.send({ id: this.product.id, status: 'disabled' })
176176
.expect(200)
177177
.then(() => app.models.Product.findById(this.product.id))
178-
.then(product => expect(product.status).to.equal('temp'))
178+
.then(product => expect(product.status).to.equal('pending'))
179179
})
180180
})
181181

0 commit comments

Comments
 (0)