Skip to content

Commit acda97e

Browse files
committed
Merge branch 'facultymatt-features/computed-props'
2 parents fc2a805 + 9a01531 commit acda97e

File tree

4 files changed

+176
-3
lines changed

4 files changed

+176
-3
lines changed

src/module.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ var RMModule = angular.module('restmod', ['ng', 'platanus.inflector']);
1212
*/
1313
RMModule.provider('restmod', [function() {
1414

15-
var BASE_CHAIN = ['RMBuilderExt', 'RMBuilderRelations'];
15+
var BASE_CHAIN = ['RMBuilderExt', 'RMBuilderRelations', 'RMBuilderComputed'];
1616

1717
function wrapInInvoke(_mixin) {
1818
return function(_injector) {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict';
2+
3+
RMModule.factory('RMBuilderComputed', ['restmod',
4+
function(restmod) {
5+
/**
6+
* @class RMBuilderComputedApi
7+
*
8+
* @description
9+
*
10+
* Builder DSL extension to build computed properties.
11+
*
12+
* A computed property is a "virtual" property which is created using
13+
* other model properties. For example, a user has a firstName and lastName,
14+
* A computed property, fullName, is generated from the two.
15+
*
16+
* Adds the following property modifiers:
17+
* * `computed` function will be assigned as getter to Model, maps to {@link RMBuilderComputedApi#attrAsComputed}
18+
*
19+
*/
20+
var EXT = {
21+
22+
/**
23+
* @memberof RMBuilderComputedApi#
24+
*
25+
* @description Registers a model computed property
26+
*
27+
* @param {string} _attr Attribute name
28+
* @param {function} _fn Function that returns the desired attribute value when run.
29+
* @return {BuilderApi} self
30+
*/
31+
attrAsComputed: function(_attr, _fn) {
32+
this.attrComputed(_attr, _fn);
33+
return this;
34+
}
35+
};
36+
37+
return restmod.mixin(function() {
38+
this.extend('attrAsComputed', EXT.attrAsComputed, ['computed']);
39+
});
40+
}
41+
]);

src/module/factory.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ RMModule.factory('RMModelFactory', ['$injector', 'inflector', 'RMUtils', 'RMScop
2525
},
2626
serializer = new Serializer(Model),
2727
defaults = [], // attribute defaults as an array of [key, value]
28+
computes = [], // computed attributes
2829
meta = {}, // atribute metadata
2930
hooks = {},
3031
builder; // the model builder
@@ -331,10 +332,17 @@ RMModule.factory('RMModelFactory', ['$injector', 'inflector', 'RMUtils', 'RMScop
331332

332333
// default initializer: loads the default parameter values
333334
$initialize: function() {
334-
var tmp;
335-
for(var i = 0; (tmp = defaults[i]); i++) {
335+
var tmp, i, self = this;
336+
for(i = 0; (tmp = defaults[i]); i++) {
336337
this[tmp[0]] = (typeof tmp[1] === 'function') ? tmp[1].apply(this) : tmp[1];
337338
}
339+
340+
for(i = 0; (tmp = computes[i]); i++) {
341+
Object.defineProperty(self, tmp[0], {
342+
enumerable: true,
343+
get: tmp[1]
344+
});
345+
}
338346
}
339347

340348
}, CommonApi, RecordApi, ExtendedApi);
@@ -433,6 +441,24 @@ RMModule.factory('RMModelFactory', ['$injector', 'inflector', 'RMUtils', 'RMScop
433441
return this;
434442
},
435443

444+
/**
445+
* @memberof BuilderApi#
446+
*
447+
* @description Sets a computed value for an attribute.
448+
*
449+
* Computed values are set only on object construction phase.
450+
* Computed values are always masked
451+
*
452+
* @param {string} _attr Attribute name
453+
* @param {function} _fn Function that returns value
454+
* @return {BuilderApi} self
455+
*/
456+
attrComputed: function(_attr, _fn) {
457+
computes.push([_attr, _fn]);
458+
this.attrMask(_attr, true);
459+
return this;
460+
},
461+
436462
/**
437463
* @memberof BuilderApi#
438464
*

test/computed-spec.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
'use strict';
2+
3+
describe('RMBuilderComputed', function() {
4+
5+
var $injector, restmod, RMBuilderComputed, UserModel;
6+
7+
beforeEach(module('restmod'));
8+
9+
beforeEach(module(function($provide) {
10+
$provide.factory('UserModel', function(restmod) {
11+
return restmod.model('/api/users', {
12+
firstName: ''
13+
});
14+
});
15+
}));
16+
17+
// cache entities to be used in tests
18+
beforeEach(inject(['$injector',
19+
function(_$injector) {
20+
$injector = _$injector;
21+
restmod = $injector.get('restmod');
22+
RMBuilderComputed = $injector.get('RMBuilderComputed');
23+
UserModel = restmod.model('/api/users', {
24+
firstName: ''
25+
});
26+
}
27+
]));
28+
29+
describe('computed property', function() {
30+
31+
describe('basics', function() {
32+
33+
var DeviceModel, device;
34+
35+
beforeEach(function() {
36+
DeviceModel = restmod.model('/api/devices', {
37+
vendor: 'default vendor',
38+
model: 'default model',
39+
fancyName: {
40+
computed: function() {
41+
return this.vendor + ': ' + this.model;
42+
}
43+
}
44+
});
45+
device = DeviceModel.$new().$decode();
46+
});
47+
48+
it('calculates using given function', function() {
49+
expect(device.fancyName).toEqual('default vendor: default model');
50+
});
51+
52+
it('changes when model properties update', function() {
53+
device.vendor = 'Apple';
54+
device.model = 'iPhone';
55+
expect(device.fancyName).toEqual('Apple: iPhone');
56+
});
57+
58+
it('should be masked by default',function() {
59+
var encoded = device.$encode();
60+
expect(encoded.fancyName).toBeUndefined();
61+
});
62+
63+
it('should be listed in $each',function() {
64+
var test = {};
65+
device.$each(function(v, k) { test[k] = v; });
66+
expect(test.fancyName).toBeDefined();
67+
});
68+
69+
});
70+
71+
describe('with relations', function() {
72+
73+
var DeviceModel, device;
74+
75+
beforeEach(function() {
76+
77+
DeviceModel = restmod.model('/api/devices', {
78+
vendor: '',
79+
model: '',
80+
user: {
81+
hasOne: 'UserModel'
82+
},
83+
ownedBy: {
84+
computed: function() {
85+
return this.user.firstName + '\'s ' + this.model;
86+
}
87+
}
88+
});
89+
device = DeviceModel.$new().$decode({
90+
vendor: 'Apple',
91+
model: 'Watch',
92+
user: {
93+
firstName: 'Johnny'
94+
}
95+
});
96+
});
97+
98+
it('can access related models', function() {
99+
expect(device.ownedBy).toEqual('Johnny\'s Watch');
100+
});
101+
102+
});
103+
104+
});
105+
106+
});

0 commit comments

Comments
 (0)