Skip to content

Commit fc09d51

Browse files
committed
Merge pull request #55 from jmdobry/feature-methods
feature-methods
2 parents 5648638 + 09217a6 commit fc09d51

File tree

9 files changed

+122
-24
lines changed

9 files changed

+122
-24
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
- #30, #48 - DSCacheFactory integration
55
- #49 - DS.bindOne($scope, prop, resourceName, id)
66
- #50 - DS.bindAll($scope, prop, resourceName, query)
7+
- #54 - Adding functionality to resources
78

89
##### 0.8.1 - 02 May 2014
910

dist/angular-data.js

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2501,6 +2501,10 @@ BaseConfig.prototype.beforeUpdate = lifecycleNoop;
25012501
BaseConfig.prototype.afterUpdate = lifecycleNoop;
25022502
BaseConfig.prototype.beforeDestroy = lifecycleNoop;
25032503
BaseConfig.prototype.afterDestroy = lifecycleNoop;
2504+
BaseConfig.prototype.beforeInject = function () {
2505+
};
2506+
BaseConfig.prototype.afterInject = function () {
2507+
};
25042508

25052509
/**
25062510
* @doc function
@@ -2531,6 +2535,8 @@ function DSProvider() {
25312535
* - `{function}` - `afterUpdate` - See [](). Default: No-op
25322536
* - `{function}` - `beforeDestroy` - See [](). Default: No-op
25332537
* - `{function}` - `afterDestroy` - See [](). Default: No-op
2538+
* - `{function}` - `beforeInject` - See [](). Default: No-op
2539+
* - `{function}` - `afterInject` - See [](). Default: No-op
25342540
*/
25352541
var defaults = this.defaults = new BaseConfig();
25362542

@@ -2840,6 +2846,7 @@ function changes(resourceName, id) {
28402846
module.exports = changes;
28412847

28422848
},{}],44:[function(require,module,exports){
2849+
/*jshint evil:true*/
28432850
var errorPrefix = 'DS.defineResource(definition): ';
28442851

28452852
function Resource(utils, options) {
@@ -2892,6 +2899,10 @@ function Resource(utils, options) {
28922899
* - `{string="id"}` - `idAttribute` - The attribute that specifies the primary key for this resource.
28932900
* - `{string=}` - `endpoint` - The attribute that specifies the primary key for this resource. Default is the value of `name`.
28942901
* - `{string=}` - `baseUrl` - The url relative to which all AJAX requests will be made.
2902+
* - `{object=}` - `methods` - If provided, items of this resource will be wrapped in a constructor function that is
2903+
* empty save for the attributes in this option which will be mixed in to the constructor function prototype. Enabling
2904+
* this feature for this resource will incur a slight performance penalty, but allows you to give custom behavior to what
2905+
* are now "instances" of this resource.
28952906
* - `{function=}` - `beforeValidate` - Lifecycle hook. Overrides global. Signature: `beforeValidate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
28962907
* - `{function=}` - `validate` - Lifecycle hook. Overrides global. Signature: `validate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
28972908
* - `{function=}` - `afterValidate` - Lifecycle hook. Overrides global. Signature: `afterValidate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
@@ -2901,6 +2912,8 @@ function Resource(utils, options) {
29012912
* - `{function=}` - `afterUpdate` - Lifecycle hook. Overrides global. Signature: `afterUpdate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
29022913
* - `{function=}` - `beforeDestroy` - Lifecycle hook. Overrides global. Signature: `beforeDestroy(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
29032914
* - `{function=}` - `afterDestroy` - Lifecycle hook. Overrides global. Signature: `afterDestroy(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
2915+
* - `{function=}` - `beforeInject` - Lifecycle hook. Overrides global. Signature: `beforeInject(resourceName, attrs)`.
2916+
* - `{function=}` - `afterInject` - Lifecycle hook. Overrides global. Signature: `afterInject(resourceName, attrs)`.
29042917
*/
29052918
function defineResource(definition) {
29062919
if (this.utils.isString(definition)) {
@@ -2924,24 +2937,32 @@ function defineResource(definition) {
29242937
Resource.prototype = this.defaults;
29252938
this.definitions[definition.name] = new Resource(this.utils, definition);
29262939

2927-
var _this = this;
2940+
var _this = this,
2941+
def = this.definitions[definition.name];
29282942

2929-
var cache = this.cacheFactory('DS.' + definition.name, {
2930-
maxAge: definition.maxAge || null,
2931-
recycleFreq: definition.recycleFreq || 1000,
2932-
cacheFlushInterval: definition.cacheFlushInterval || null,
2933-
deleteOnExpire: definition.deleteOnExpire || 'none',
2943+
var cache = this.cacheFactory('DS.' + def.name, {
2944+
maxAge: def.maxAge || null,
2945+
recycleFreq: def.recycleFreq || 1000,
2946+
cacheFlushInterval: def.cacheFlushInterval || null,
2947+
deleteOnExpire: def.deleteOnExpire || 'none',
29342948
onExpire: function (id) {
2935-
_this.eject(definition.name, id);
2949+
_this.eject(def.name, id);
29362950
},
29372951
capacity: Number.MAX_VALUE,
29382952
storageMode: 'memory',
29392953
storageImpl: null,
29402954
disabled: false,
2941-
storagePrefix: 'DS.' + definition.name
2955+
storagePrefix: 'DS.' + def.name
29422956
});
29432957

2944-
this.store[definition.name] = {
2958+
if (def.methods) {
2959+
def.class = definition.name[0].toUpperCase() + definition.name.substring(1);
2960+
eval('function ' + def.class + '() {}');
2961+
def[def.class] = eval(def.class);
2962+
this.utils.deepMixIn(def[def.class].prototype, def.methods);
2963+
}
2964+
2965+
this.store[def.name] = {
29452966
collection: [],
29462967
completedQueries: {},
29472968
pendingQueries: {},
@@ -3648,11 +3669,12 @@ function _inject(definition, resource, attrs) {
36483669
if (!(definition.idAttribute in attrs)) {
36493670
throw new _this.errors.RuntimeError(errorPrefix + 'attrs: Must contain the property specified by `idAttribute`!');
36503671
} else {
3672+
definition.beforeInject(definition.name, attrs);
36513673
var id = attrs[definition.idAttribute],
36523674
item = this.get(definition.name, id);
36533675

36543676
if (!item) {
3655-
item = {};
3677+
item = definition.class ? new definition[definition.class]() : {};
36563678
resource.previousAttributes[id] = {};
36573679

36583680
_this.utils.deepMixIn(item, attrs);
@@ -3676,6 +3698,7 @@ function _inject(definition, resource, attrs) {
36763698
resource.observers[id].deliver();
36773699
}
36783700
resource.saved[id] = _this.utils.updateTimestamp(resource.saved[id]);
3701+
definition.afterInject(definition.name, item);
36793702
}
36803703
}
36813704
}
@@ -3748,7 +3771,11 @@ function inject(resourceName, attrs, options) {
37483771
} else {
37493772
_inject.apply(_this, [definition, resource, attrs]);
37503773
}
3751-
return attrs;
3774+
if (_this.utils.isArray(attrs)) {
3775+
return attrs;
3776+
} else {
3777+
return this.get(resourceName, attrs[definition.idAttribute]);
3778+
}
37523779
} catch (err) {
37533780
if (!(err instanceof this.errors.RuntimeError)) {
37543781
throw new this.errors.UnhandledError(err);

dist/angular-data.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

karma.start.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ beforeEach(function (done) {
6767
lifecycle.afterDestroy.callCount += 1;
6868
cb(null, attrs);
6969
};
70+
lifecycle.beforeInject = function () {
71+
lifecycle.beforeInject.callCount += 1;
72+
};
73+
lifecycle.afterInject = function () {
74+
lifecycle.afterInject.callCount += 1;
75+
};
7076
module('app', function (_DSProvider_) {
7177
DSProvider = _DSProvider_;
7278
DSProvider.defaults.baseUrl = 'http://test.angular-cache.com';
@@ -79,6 +85,8 @@ beforeEach(function (done) {
7985
DSProvider.defaults.afterUpdate = lifecycle.afterUpdate;
8086
DSProvider.defaults.beforeDestroy = lifecycle.beforeDestroy;
8187
DSProvider.defaults.afterDestroy = lifecycle.afterDestroy;
88+
DSProvider.defaults.beforeInject = lifecycle.beforeInject;
89+
DSProvider.defaults.afterInject = lifecycle.afterInject;
8290
});
8391
inject(function (_$rootScope_, _$q_, _$httpBackend_, _DS_, _$log_) {
8492
// Setup global mocks
@@ -101,6 +109,8 @@ beforeEach(function (done) {
101109
lifecycle.afterUpdate.callCount = 0;
102110
lifecycle.beforeDestroy.callCount = 0;
103111
lifecycle.afterDestroy.callCount = 0;
112+
lifecycle.beforeInject.callCount = 0;
113+
lifecycle.afterInject.callCount = 0;
104114

105115
p1 = { author: 'John', age: 30, id: 5 };
106116
p2 = { author: 'Sally', age: 31, id: 6 };

src/datastore/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ BaseConfig.prototype.beforeUpdate = lifecycleNoop;
5252
BaseConfig.prototype.afterUpdate = lifecycleNoop;
5353
BaseConfig.prototype.beforeDestroy = lifecycleNoop;
5454
BaseConfig.prototype.afterDestroy = lifecycleNoop;
55+
BaseConfig.prototype.beforeInject = function () {
56+
};
57+
BaseConfig.prototype.afterInject = function () {
58+
};
5559

5660
/**
5761
* @doc function
@@ -82,6 +86,8 @@ function DSProvider() {
8286
* - `{function}` - `afterUpdate` - See [](). Default: No-op
8387
* - `{function}` - `beforeDestroy` - See [](). Default: No-op
8488
* - `{function}` - `afterDestroy` - See [](). Default: No-op
89+
* - `{function}` - `beforeInject` - See [](). Default: No-op
90+
* - `{function}` - `afterInject` - See [](). Default: No-op
8591
*/
8692
var defaults = this.defaults = new BaseConfig();
8793

src/datastore/sync_methods/defineResource.js

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/*jshint evil:true*/
12
var errorPrefix = 'DS.defineResource(definition): ';
23

34
function Resource(utils, options) {
@@ -50,6 +51,10 @@ function Resource(utils, options) {
5051
* - `{string="id"}` - `idAttribute` - The attribute that specifies the primary key for this resource.
5152
* - `{string=}` - `endpoint` - The attribute that specifies the primary key for this resource. Default is the value of `name`.
5253
* - `{string=}` - `baseUrl` - The url relative to which all AJAX requests will be made.
54+
* - `{object=}` - `methods` - If provided, items of this resource will be wrapped in a constructor function that is
55+
* empty save for the attributes in this option which will be mixed in to the constructor function prototype. Enabling
56+
* this feature for this resource will incur a slight performance penalty, but allows you to give custom behavior to what
57+
* are now "instances" of this resource.
5358
* - `{function=}` - `beforeValidate` - Lifecycle hook. Overrides global. Signature: `beforeValidate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
5459
* - `{function=}` - `validate` - Lifecycle hook. Overrides global. Signature: `validate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
5560
* - `{function=}` - `afterValidate` - Lifecycle hook. Overrides global. Signature: `afterValidate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
@@ -59,6 +64,8 @@ function Resource(utils, options) {
5964
* - `{function=}` - `afterUpdate` - Lifecycle hook. Overrides global. Signature: `afterUpdate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
6065
* - `{function=}` - `beforeDestroy` - Lifecycle hook. Overrides global. Signature: `beforeDestroy(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
6166
* - `{function=}` - `afterDestroy` - Lifecycle hook. Overrides global. Signature: `afterDestroy(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
67+
* - `{function=}` - `beforeInject` - Lifecycle hook. Overrides global. Signature: `beforeInject(resourceName, attrs)`.
68+
* - `{function=}` - `afterInject` - Lifecycle hook. Overrides global. Signature: `afterInject(resourceName, attrs)`.
6269
*/
6370
function defineResource(definition) {
6471
if (this.utils.isString(definition)) {
@@ -82,24 +89,32 @@ function defineResource(definition) {
8289
Resource.prototype = this.defaults;
8390
this.definitions[definition.name] = new Resource(this.utils, definition);
8491

85-
var _this = this;
92+
var _this = this,
93+
def = this.definitions[definition.name];
8694

87-
var cache = this.cacheFactory('DS.' + definition.name, {
88-
maxAge: definition.maxAge || null,
89-
recycleFreq: definition.recycleFreq || 1000,
90-
cacheFlushInterval: definition.cacheFlushInterval || null,
91-
deleteOnExpire: definition.deleteOnExpire || 'none',
95+
var cache = this.cacheFactory('DS.' + def.name, {
96+
maxAge: def.maxAge || null,
97+
recycleFreq: def.recycleFreq || 1000,
98+
cacheFlushInterval: def.cacheFlushInterval || null,
99+
deleteOnExpire: def.deleteOnExpire || 'none',
92100
onExpire: function (id) {
93-
_this.eject(definition.name, id);
101+
_this.eject(def.name, id);
94102
},
95103
capacity: Number.MAX_VALUE,
96104
storageMode: 'memory',
97105
storageImpl: null,
98106
disabled: false,
99-
storagePrefix: 'DS.' + definition.name
107+
storagePrefix: 'DS.' + def.name
100108
});
101109

102-
this.store[definition.name] = {
110+
if (def.methods) {
111+
def.class = definition.name[0].toUpperCase() + definition.name.substring(1);
112+
eval('function ' + def.class + '() {}');
113+
def[def.class] = eval(def.class);
114+
this.utils.deepMixIn(def[def.class].prototype, def.methods);
115+
}
116+
117+
this.store[def.name] = {
103118
collection: [],
104119
completedQueries: {},
105120
pendingQueries: {},

src/datastore/sync_methods/inject.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@ function _inject(definition, resource, attrs) {
2929
if (!(definition.idAttribute in attrs)) {
3030
throw new _this.errors.RuntimeError(errorPrefix + 'attrs: Must contain the property specified by `idAttribute`!');
3131
} else {
32+
definition.beforeInject(definition.name, attrs);
3233
var id = attrs[definition.idAttribute],
3334
item = this.get(definition.name, id);
3435

3536
if (!item) {
36-
item = {};
37+
item = definition.class ? new definition[definition.class]() : {};
3738
resource.previousAttributes[id] = {};
3839

3940
_this.utils.deepMixIn(item, attrs);
@@ -57,6 +58,7 @@ function _inject(definition, resource, attrs) {
5758
resource.observers[id].deliver();
5859
}
5960
resource.saved[id] = _this.utils.updateTimestamp(resource.saved[id]);
61+
definition.afterInject(definition.name, item);
6062
}
6163
}
6264
}
@@ -129,7 +131,11 @@ function inject(resourceName, attrs, options) {
129131
} else {
130132
_inject.apply(_this, [definition, resource, attrs]);
131133
}
132-
return attrs;
134+
if (_this.utils.isArray(attrs)) {
135+
return attrs;
136+
} else {
137+
return this.get(resourceName, attrs[definition.idAttribute]);
138+
}
133139
} catch (err) {
134140
if (!(err instanceof this.errors.RuntimeError)) {
135141
throw new this.errors.UnhandledError(err);

test/integration/datastore/sync_methods/defineResource.test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,34 @@ describe('DS.defineResource(definition)', function () {
8383
assert.equal(callCount, 1, 'overridden validate should have been called once');
8484
assert.equal(lifecycle.validate.callCount, 0, 'global validate should not have been called');
8585
});
86+
it('should allow custom behavior to be applied to resources', function () {
87+
DS.defineResource({
88+
name: 'user',
89+
methods: {
90+
fullName: function () {
91+
return this.first + ' ' + this.last;
92+
}
93+
}
94+
});
95+
96+
DS.inject('user', {
97+
first: 'John',
98+
last: 'Anderson',
99+
id: 1
100+
});
101+
102+
var user = DS.get('user', 1);
103+
104+
assert.deepEqual(JSON.stringify(user), JSON.stringify({
105+
first: 'John',
106+
last: 'Anderson',
107+
id: 1
108+
}));
109+
assert.equal(user.fullName(), 'John Anderson');
110+
assert.isTrue(user instanceof DS.definitions.user[DS.definitions.user.class]);
111+
assert.equal(DS.definitions.user.class, 'User');
112+
assert.equal(DS.definitions.user[DS.definitions.user.class].name, 'User');
113+
assert.equal(lifecycle.beforeInject.callCount, 1, 'beforeInject should have been called');
114+
assert.equal(lifecycle.afterInject.callCount, 1, 'afterInject should have been called');
115+
});
86116
});

test/integration/datastore/sync_methods/filter.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ describe('DS.filter(resourceName, params[, options])', function () {
111111
DS.inject('post', p4);
112112
}, Error, 'should not throw an error');
113113

114+
assert.equal(lifecycle.beforeInject.callCount, 4);
115+
assert.equal(lifecycle.afterInject.callCount, 4);
116+
114117
var params = {
115118
query: {
116119
where: {

0 commit comments

Comments
 (0)