Skip to content

Commit d585e76

Browse files
committed
Merge branch 'features/computed-props' of https://github.com/facultymatt/angular-restmod into facultymatt-features/computed-props
2 parents fc2a805 + daf3b71 commit d585e76

File tree

4 files changed

+163
-2
lines changed

4 files changed

+163
-2
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: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ RMModule.factory('RMModelFactory', ['$injector', 'inflector', 'RMUtils', 'RMScop
2424
urlPrefix: null
2525
},
2626
serializer = new Serializer(Model),
27+
computes = {},
2728
defaults = [], // attribute defaults as an array of [key, value]
2829
meta = {}, // atribute metadata
2930
hooks = {},
@@ -331,10 +332,19 @@ RMModule.factory('RMModelFactory', ['$injector', 'inflector', 'RMUtils', 'RMScop
331332

332333
// default initializer: loads the default parameter values
333334
$initialize: function() {
334-
var tmp;
335+
var tmp, self = this;
335336
for(var i = 0; (tmp = defaults[i]); i++) {
336337
this[tmp[0]] = (typeof tmp[1] === 'function') ? tmp[1].apply(this) : tmp[1];
337338
}
339+
Object.keys(computes).forEach(function(key) {
340+
//console.log(self);
341+
Object.defineProperty(self, key, {
342+
enumerable: true,
343+
get: function() {
344+
return computes[key].apply(self);
345+
}
346+
});
347+
});
338348
}
339349

340350
}, CommonApi, RecordApi, ExtendedApi);
@@ -433,6 +443,22 @@ RMModule.factory('RMModelFactory', ['$injector', 'inflector', 'RMUtils', 'RMScop
433443
return this;
434444
},
435445

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

test/computed-spec.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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, restmodProvider) {
10+
$provide.factory('UserModel', function(restmod) {
11+
return restmod.model('/api/users', {
12+
firstName: ''
13+
});
14+
});
15+
}));
16+
17+
18+
// cache entities to be used in tests
19+
beforeEach(inject(['$injector',
20+
function(_$injector) {
21+
$injector = _$injector;
22+
restmod = $injector.get('restmod');
23+
RMBuilderComputed = $injector.get('RMBuilderComputed');
24+
UserModel = restmod.model('/api/users', {
25+
firstName: ''
26+
});
27+
}
28+
]));
29+
30+
describe('computed property', function() {
31+
32+
describe('basics', function() {
33+
34+
var DeviceModel, device;
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+
});
59+
60+
describe('with relations', function() {
61+
62+
var DeviceModel, UserModel, device;
63+
beforeEach(function() {
64+
65+
DeviceModel = restmod.model('/api/devices', {
66+
vendor: '',
67+
model: '',
68+
user: {
69+
hasOne: 'UserModel'
70+
},
71+
ownedBy: {
72+
computed: function() {
73+
return this.user.firstName + "'s " + this.model;
74+
}
75+
}
76+
});
77+
device = DeviceModel.$new().$decode({
78+
vendor: 'Apple',
79+
model: 'Watch',
80+
user: {
81+
firstName: 'Johnny'
82+
}
83+
});
84+
});
85+
86+
it('can access related models', function() {
87+
expect(device.ownedBy).toEqual("Johnny's Watch");
88+
});
89+
90+
});
91+
92+
});
93+
94+
});

0 commit comments

Comments
 (0)