Skip to content

Commit d6b39e2

Browse files
committed
feat(model): replaces SyncMask by string mask.
BREAKING CHANGE: SyncMask is no longer available. The attrMask method and the ignore attribute modifier now take a string, see attrMask documentation for more information. Closes #43
1 parent 9d36bdd commit d6b39e2

File tree

4 files changed

+98
-81
lines changed

4 files changed

+98
-81
lines changed

src/module/restmod.js

Lines changed: 62 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,6 @@
11
'use strict';
22

3-
/**
4-
* @class SyncMask
5-
* @memberOf constants
6-
*
7-
* @description The object property synchronization mask.
8-
*/
9-
var SyncMask = {
10-
NONE: 0x00,
11-
ALL: 0xFFFF,
12-
SYSTEM_ALL: 0x1FFFF,
13-
14-
SYSTEM: 0x10000,
15-
16-
DECODE_CREATE: 0x0001,
17-
DECODE_UPDATE: 0x0002,
18-
DECODE_USER: 0x0004,
19-
DECODE_SAVE: 0x0003,
20-
21-
ENCODE_CREATE: 0x0100,
22-
ENCODE_UPDATE: 0x0200,
23-
ENCODE_USER: 0x0400,
24-
ENCODE_SAVE: 0x0300,
25-
26-
// Compound masks
27-
DECODE: 0x00FF,
28-
ENCODE: 0xFF00,
29-
CREATE: 0x0101,
30-
UPDATE: 0x0202,
31-
USER: 0x0404,
32-
SAVE: 0x0303
33-
};
34-
35-
// Cache some angular stuff
3+
// Preload some angular stuff
364
var bind = angular.bind,
375
forEach = angular.forEach,
386
extend = angular.extend,
@@ -41,6 +9,13 @@ var bind = angular.bind,
419
isFunction = angular.isFunction,
4210
arraySlice = Array.prototype.slice;
4311

12+
// Constants
13+
var CREATE_MASK = 'C',
14+
UPDATE_MASK = 'U',
15+
READ_MASK = 'R',
16+
WRITE_MASK = 'CU',
17+
FULL_MASK = 'CRU';
18+
4419
/**
4520
* @class $restmodProvider
4621
* @memberOf providers
@@ -214,32 +189,34 @@ angular.module('plRestmod').provider('$restmod', function() {
214189
// recursive transformation function, used by $decode and $encode.
215190
function transform(_data, _ctx, _prefix, _mask, _decode, _into) {
216191

217-
var key, decodedName, encodedName, fullName, filter, value, result = _into || {};
192+
var key, decodedName, encodedName, fullName, mask, filter, value, result = _into || {};
218193

219194
for(key in _data) {
220195
if(_data.hasOwnProperty(key) && key[0] !== '$') {
221196

222197
decodedName = (_decode && nameDecoder) ? nameDecoder(key) : key;
223198
fullName = _prefix + decodedName;
224199

225-
// check if property is masked for this operation
226-
if(!((masks[fullName] || 0) & _mask)) {
227-
228-
value = _data[key];
229-
filter = _decode ? decoders[fullName] : encoders[fullName];
200+
// skip property if masked for this operation
201+
mask = masks[fullName];
202+
if(mask && mask.indexOf(_mask) !== -1) {
203+
continue;
204+
}
230205

231-
if(filter) {
232-
value = filter.call(_ctx, value);
233-
if(value === undefined) continue; // ignore value if filter returns undefined
234-
} else if(typeof value === 'object' && value &&
235-
(_decode || typeof value.toJSON !== 'function')) {
236-
// IDEA: make extended decoding/encoding optional, could be a little taxxing for some apps
237-
value = transformExtended(value, _ctx, fullName, _mask, _decode);
238-
}
206+
value = _data[key];
207+
filter = _decode ? decoders[fullName] : encoders[fullName];
239208

240-
encodedName = (!_decode && nameEncoder) ? nameEncoder(decodedName) : decodedName;
241-
result[encodedName] = value;
209+
if(filter) {
210+
value = filter.call(_ctx, value);
211+
if(value === undefined) continue; // ignore value if filter returns undefined
212+
} else if(typeof value === 'object' && value &&
213+
(_decode || typeof value.toJSON !== 'function')) {
214+
// IDEA: make extended decoding/encoding optional, could be a little taxing for some apps
215+
value = transformExtended(value, _ctx, fullName, _mask, _decode);
242216
}
217+
218+
encodedName = (!_decode && nameEncoder) ? nameEncoder(decodedName) : decodedName;
219+
result[encodedName] = value;
243220
}
244221
}
245222

@@ -391,7 +368,11 @@ angular.module('plRestmod').provider('$restmod', function() {
391368

392369
// sets an attribute mask at runtime
393370
Model.$$setMask = function(_attr, _mask) {
394-
masks[_attr] = _mask;
371+
if(!_mask) {
372+
delete masks[_attr];
373+
} else {
374+
masks[_attr] = _mask === true ? FULL_MASK : _mask;
375+
}
395376
};
396377

397378
// registers a new global hook
@@ -594,11 +575,10 @@ angular.module('plRestmod').provider('$restmod', function() {
594575
* @description Feed raw data to this instance.
595576
*
596577
* @param {object} _raw Raw data to be fed
597-
* @param {string} _mask Action mask
598578
* @return {Model} this
599579
*/
600580
$decode: function(_raw, _mask) {
601-
transform(_raw, this, '', _mask || SyncMask.DECODE_USER, true, this);
581+
transform(_raw, this, '', _mask || READ_MASK, true, this);
602582
if(!this.$pk) this.$pk = Model.$inferKey(this); // TODO: improve this, warn if key changes
603583
callback('after-feed', this, _raw);
604584
return this;
@@ -613,7 +593,7 @@ angular.module('plRestmod').provider('$restmod', function() {
613593
* @return {Model} this
614594
*/
615595
$encode: function(_mask) {
616-
var raw = transform(this, this, '', _mask || SyncMask.ENCODE_USER, false);
596+
var raw = transform(this, this, '', _mask || CREATE_MASK, false);
617597
callback('before-render', this, raw);
618598
return raw;
619599
},
@@ -663,12 +643,12 @@ angular.module('plRestmod').provider('$restmod', function() {
663643

664644
if(url) {
665645
// If bound, update
666-
request = { method: 'PUT', url: url, data: this.$encode(SyncMask.ENCODE_UPDATE) };
646+
request = { method: 'PUT', url: url, data: this.$encode(CREATE_MASK) };
667647
callback('before-update', this, request);
668648
callback('before-save', this, request);
669649
return this.$send(request, function(_response) {
670650
var data = _response.data;
671-
if (data && !isArray(data)) this.$decode(data, SyncMask.DECODE_UPDATE);
651+
if (data && !isArray(data)) this.$decode(data);
672652
callback('after-update', this, _response);
673653
callback('after-save', this, _response);
674654
}, function(_response) {
@@ -679,12 +659,12 @@ angular.module('plRestmod').provider('$restmod', function() {
679659
// If not bound create.
680660
url = this.$scope.$createUrlFor ? this.$scope.$createUrlFor(this.$pk) : (this.$scope.$url && this.$scope.$url());
681661
if(!url) throw new Error('Create is not supported by this resource');
682-
request = { method: 'POST', url: url, data: this.$encode(SyncMask.ENCODE_CREATE) };
662+
request = { method: 'POST', url: url, data: this.$encode(UPDATE_MASK) };
683663
callback('before-save', this, request);
684664
callback('before-create', this, request);
685665
return this.$send(request, function(_response) {
686666
var data = _response.data;
687-
if (data && !isArray(data)) this.$decode(data, SyncMask.DECODE_CREATE);
667+
if (data && !isArray(data)) this.$decode(data);
688668
callback('after-create', this, _response);
689669
callback('after-save', this, _response);
690670
}, function(_response) {
@@ -1399,23 +1379,28 @@ angular.module('plRestmod').provider('$restmod', function() {
13991379
*
14001380
* @description Sets an attribute mask.
14011381
*
1382+
* An attribute mask prevents the attribute to be loaded from or sent to the server on certain operations.
1383+
*
1384+
* The attribute mask is a string composed by:
1385+
* * C: To prevent attribute from being sent on create
1386+
* * R: To prevent attribute from being loaded from server
1387+
* * U: To prevent attribute from being sent on update
1388+
*
1389+
* For example, the following will prevent an attribute to be send on create or update:
1390+
*
1391+
* ```javascript
1392+
* builder.attrMask('readOnly', 'CU');
1393+
* ```
1394+
*
1395+
* If a true boolean value is passed as mask, then 'CRU' will be used
1396+
* If a false boolean valus is passed as mask, then mask will be removed
1397+
*
14021398
* @param {string} _attr Attribute name
1403-
* @param {boolean|integer} _mask Ignore mask or true to use SyncMask.ALL
1404-
* @param {boolean} _reset If set to true, old mask is reset.
1399+
* @param {boolean|string} _mask Attribute mask
14051400
* @return {ModelBuilder} self
14061401
*/
1407-
attrMask: function(_attr, _mask, _reset) {
1408-
1409-
if(_mask === true) {
1410-
masks[_attr] = SyncMask.ALL;
1411-
} else if(_mask === false) {
1412-
delete masks[_attr];
1413-
} else if(_reset) {
1414-
masks[_attr] = _mask;
1415-
} else {
1416-
masks[_attr] |= _mask;
1417-
}
1418-
1402+
attrMask: function(_attr, _mask) {
1403+
Model.$$setMask(_attr, _mask);
14191404
return this;
14201405
},
14211406

@@ -1508,7 +1493,7 @@ angular.module('plRestmod').provider('$restmod', function() {
15081493
_model = $injector.get(_model);
15091494

15101495
if(_inverseOf) {
1511-
_model.$$setMask(_inverseOf, SyncMask.ENCODE);
1496+
_model.$$setMask(_inverseOf, WRITE_MASK);
15121497
}
15131498
}
15141499

@@ -1533,7 +1518,7 @@ angular.module('plRestmod').provider('$restmod', function() {
15331518
// simple support for inline data, TODO: maybe deprecate this.
15341519
}).attrDecoder(_source || _url || _attr, function(_raw) {
15351520
this[_attr].$reset().$feed(_raw);
1536-
}).attrMask(_attr, SyncMask.ENCODE);
1521+
}).attrMask(_attr, WRITE_MASK);
15371522
},
15381523

15391524
/**
@@ -1557,7 +1542,7 @@ angular.module('plRestmod').provider('$restmod', function() {
15571542
_model = $injector.get(_model);
15581543

15591544
if(_inverseOf) {
1560-
_model.$$setMask(_inverseOf, SyncMask.ENCODE);
1545+
_model.$$setMask(_inverseOf, WRITE_MASK);
15611546
}
15621547
}
15631548

@@ -1576,7 +1561,7 @@ angular.module('plRestmod').provider('$restmod', function() {
15761561
.attrDecoder(_source || _url || _attr, function(_raw) {
15771562
this[_attr].$decode(_raw);
15781563
})
1579-
.attrMask(_attr, SyncMask.ENCODE);
1564+
.attrMask(_attr, WRITE_MASK);
15801565
},
15811566

15821567
/**
@@ -1598,7 +1583,7 @@ angular.module('plRestmod').provider('$restmod', function() {
15981583
var watch = _inline ? (_source || _attr) : (_key || (_attr + 'Id'));
15991584
this
16001585
.attrDefault(_attr, null)
1601-
.attrMask(_attr, SyncMask.ENCODE)
1586+
.attrMask(_attr, WRITE_MASK)
16021587
.attrDecoder(watch , function(_raw) {
16031588

16041589
// load model
@@ -1783,7 +1768,5 @@ angular.module('plRestmod').provider('$restmod', function() {
17831768
}])
17841769
.factory('mixin', ['$restmod', function($restmod) {
17851770
return $restmod.mixin;
1786-
}])
1787-
// make SyncMask available as constant
1788-
.constant('SyncMask', SyncMask);
1771+
}]);
17891772

src/plugins/debounced.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
var isObject = angular.isObject;
3737

38-
angular.module('plRestmod').factory('DebouncedModel', ['$restmod', 'SyncMask', '$timeout', '$q', function($restmod, SyncMask, $timeout, $q) {
38+
angular.module('plRestmod').factory('DebouncedModel', ['$restmod', '$timeout', '$q', function($restmod, $timeout, $q) {
3939

4040
// builds a new async save function bound to a given context and promise.
4141
function buildAsyncSaveFun(_this, _oldSave, _promise, _oldPromise) {

src/plugins/dirty.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
'use strict';
88

9-
angular.module('plRestmod').factory('DirtyModel', ['$restmod', 'SyncMask', function($restmod, SyncMask) {
9+
angular.module('plRestmod').factory('DirtyModel', ['$restmod', function($restmod) {
1010

1111
return $restmod.mixin(function() {
1212
this.on('after-feed', function(_original) {

test/model_spec.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,21 @@ describe('Restmod model class:', function() {
141141
expect(bike.nested[0].snakeCase).toBeDefined();
142142
});
143143

144+
it('should skip masked properties', function() {
145+
var bike = $restmod.model(null, {
146+
imMasked: { ignore: 'R' },
147+
imMaskedToo: { ignore: true },
148+
imNotMasked: { ignore: 'U' }
149+
}).$build();
150+
151+
bike.$decode({ imMasked: true, imMaskedToo: true, imNotMasked: true, imNotMaskedEither: true });
152+
153+
expect(bike.imMasked).toBeUndefined();
154+
expect(bike.imMaskedToo).toBeUndefined();
155+
expect(bike.imNotMasked).toBeDefined();
156+
expect(bike.imNotMaskedEither).toBeDefined();
157+
});
158+
144159
it('should apply registered decoders', function() {
145160
var bike = $restmod.model(null, function() {
146161
this.attrDecoder('size', function(_val) { return _val === 'S' ? 'small' : 'regular'; });
@@ -187,6 +202,25 @@ describe('Restmod model class:', function() {
187202
expect(raw.user.last_name).toBeDefined();
188203
});
189204

205+
it('should skip masked properties', function() {
206+
var bike = $restmod.model(null, {
207+
imMasked: { ignore: 'C' },
208+
imMaskedToo: { ignore: true },
209+
imNotMasked: { ignore: 'R' }
210+
}).$build();
211+
212+
angular.extend(bike, { imMasked: true, imMaskedToo: true, imNotMasked: true, imNotMaskedEither: true });
213+
var raw = bike.$encode('C');
214+
expect(raw.im_masked).toBeUndefined();
215+
expect(raw.im_masked_too).toBeUndefined();
216+
expect(raw.im_not_masked).toBeDefined();
217+
expect(raw.im_not_masked_either).toBeDefined();
218+
219+
var raw = bike.$encode('U');
220+
expect(raw.im_masked).toBeDefined(); // not this time!
221+
expect(raw.im_masked_too).toBeUndefined();
222+
});
223+
190224
it('should apply registered encoders', function() {
191225
var bike = $restmod.model(null, function() {
192226
this.attrEncoder('size', function(_val) { return _val === 'small' ? 'S' : 'M'; });

0 commit comments

Comments
 (0)