Skip to content

Commit 4e85954

Browse files
committed
Closes #54.
1 parent 5648638 commit 4e85954

File tree

9 files changed

+116
-24
lines changed

9 files changed

+116
-24
lines changed

CHANGELOG.md

+1
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

+36-11
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

@@ -2892,6 +2898,10 @@ function Resource(utils, options) {
28922898
* - `{string="id"}` - `idAttribute` - The attribute that specifies the primary key for this resource.
28932899
* - `{string=}` - `endpoint` - The attribute that specifies the primary key for this resource. Default is the value of `name`.
28942900
* - `{string=}` - `baseUrl` - The url relative to which all AJAX requests will be made.
2901+
* - `{object=}` - `methods` - If provided, items of this resource will be wrapped in a constructor function that is
2902+
* empty save for the attributes in this option which will be mixed in to the constructor function prototype. Enabling
2903+
* this feature for this resource will incur a slight performance penalty, but allows you to give custom behavior to what
2904+
* are now "instances" of this resource.
28952905
* - `{function=}` - `beforeValidate` - Lifecycle hook. Overrides global. Signature: `beforeValidate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
28962906
* - `{function=}` - `validate` - Lifecycle hook. Overrides global. Signature: `validate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
28972907
* - `{function=}` - `afterValidate` - Lifecycle hook. Overrides global. Signature: `afterValidate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
@@ -2901,6 +2911,8 @@ function Resource(utils, options) {
29012911
* - `{function=}` - `afterUpdate` - Lifecycle hook. Overrides global. Signature: `afterUpdate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
29022912
* - `{function=}` - `beforeDestroy` - Lifecycle hook. Overrides global. Signature: `beforeDestroy(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
29032913
* - `{function=}` - `afterDestroy` - Lifecycle hook. Overrides global. Signature: `afterDestroy(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
2914+
* - `{function=}` - `beforeInject` - Lifecycle hook. Overrides global. Signature: `beforeInject(resourceName, attrs)`.
2915+
* - `{function=}` - `afterInject` - Lifecycle hook. Overrides global. Signature: `afterInject(resourceName, attrs)`.
29042916
*/
29052917
function defineResource(definition) {
29062918
if (this.utils.isString(definition)) {
@@ -2924,24 +2936,31 @@ function defineResource(definition) {
29242936
Resource.prototype = this.defaults;
29252937
this.definitions[definition.name] = new Resource(this.utils, definition);
29262938

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

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',
2942+
var cache = this.cacheFactory('DS.' + def.name, {
2943+
maxAge: def.maxAge || null,
2944+
recycleFreq: def.recycleFreq || 1000,
2945+
cacheFlushInterval: def.cacheFlushInterval || null,
2946+
deleteOnExpire: def.deleteOnExpire || 'none',
29342947
onExpire: function (id) {
2935-
_this.eject(definition.name, id);
2948+
_this.eject(def.name, id);
29362949
},
29372950
capacity: Number.MAX_VALUE,
29382951
storageMode: 'memory',
29392952
storageImpl: null,
29402953
disabled: false,
2941-
storagePrefix: 'DS.' + definition.name
2954+
storagePrefix: 'DS.' + def.name
29422955
});
29432956

2944-
this.store[definition.name] = {
2957+
if (def.methods) {
2958+
def.factory = function () {
2959+
};
2960+
this.utils.deepMixIn(def.factory.prototype, def.methods);
2961+
}
2962+
2963+
this.store[def.name] = {
29452964
collection: [],
29462965
completedQueries: {},
29472966
pendingQueries: {},
@@ -3648,11 +3667,12 @@ function _inject(definition, resource, attrs) {
36483667
if (!(definition.idAttribute in attrs)) {
36493668
throw new _this.errors.RuntimeError(errorPrefix + 'attrs: Must contain the property specified by `idAttribute`!');
36503669
} else {
3670+
definition.beforeInject(definition.name, attrs);
36513671
var id = attrs[definition.idAttribute],
36523672
item = this.get(definition.name, id);
36533673

36543674
if (!item) {
3655-
item = {};
3675+
item = definition.factory ? new definition.factory() : {};
36563676
resource.previousAttributes[id] = {};
36573677

36583678
_this.utils.deepMixIn(item, attrs);
@@ -3676,6 +3696,7 @@ function _inject(definition, resource, attrs) {
36763696
resource.observers[id].deliver();
36773697
}
36783698
resource.saved[id] = _this.utils.updateTimestamp(resource.saved[id]);
3699+
definition.afterInject(definition.name, item);
36793700
}
36803701
}
36813702
}
@@ -3748,7 +3769,11 @@ function inject(resourceName, attrs, options) {
37483769
} else {
37493770
_inject.apply(_this, [definition, resource, attrs]);
37503771
}
3751-
return attrs;
3772+
if (_this.utils.isArray(attrs)) {
3773+
return attrs;
3774+
} else {
3775+
return this.get(resourceName, attrs[definition.idAttribute]);
3776+
}
37523777
} catch (err) {
37533778
if (!(err instanceof this.errors.RuntimeError)) {
37543779
throw new this.errors.UnhandledError(err);

dist/angular-data.min.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

karma.start.js

+10
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

+6
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

+22-9
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ function Resource(utils, options) {
5050
* - `{string="id"}` - `idAttribute` - The attribute that specifies the primary key for this resource.
5151
* - `{string=}` - `endpoint` - The attribute that specifies the primary key for this resource. Default is the value of `name`.
5252
* - `{string=}` - `baseUrl` - The url relative to which all AJAX requests will be made.
53+
* - `{object=}` - `methods` - If provided, items of this resource will be wrapped in a constructor function that is
54+
* empty save for the attributes in this option which will be mixed in to the constructor function prototype. Enabling
55+
* this feature for this resource will incur a slight performance penalty, but allows you to give custom behavior to what
56+
* are now "instances" of this resource.
5357
* - `{function=}` - `beforeValidate` - Lifecycle hook. Overrides global. Signature: `beforeValidate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
5458
* - `{function=}` - `validate` - Lifecycle hook. Overrides global. Signature: `validate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
5559
* - `{function=}` - `afterValidate` - Lifecycle hook. Overrides global. Signature: `afterValidate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
@@ -59,6 +63,8 @@ function Resource(utils, options) {
5963
* - `{function=}` - `afterUpdate` - Lifecycle hook. Overrides global. Signature: `afterUpdate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
6064
* - `{function=}` - `beforeDestroy` - Lifecycle hook. Overrides global. Signature: `beforeDestroy(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
6165
* - `{function=}` - `afterDestroy` - Lifecycle hook. Overrides global. Signature: `afterDestroy(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`.
66+
* - `{function=}` - `beforeInject` - Lifecycle hook. Overrides global. Signature: `beforeInject(resourceName, attrs)`.
67+
* - `{function=}` - `afterInject` - Lifecycle hook. Overrides global. Signature: `afterInject(resourceName, attrs)`.
6268
*/
6369
function defineResource(definition) {
6470
if (this.utils.isString(definition)) {
@@ -82,24 +88,31 @@ function defineResource(definition) {
8288
Resource.prototype = this.defaults;
8389
this.definitions[definition.name] = new Resource(this.utils, definition);
8490

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

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',
94+
var cache = this.cacheFactory('DS.' + def.name, {
95+
maxAge: def.maxAge || null,
96+
recycleFreq: def.recycleFreq || 1000,
97+
cacheFlushInterval: def.cacheFlushInterval || null,
98+
deleteOnExpire: def.deleteOnExpire || 'none',
9299
onExpire: function (id) {
93-
_this.eject(definition.name, id);
100+
_this.eject(def.name, id);
94101
},
95102
capacity: Number.MAX_VALUE,
96103
storageMode: 'memory',
97104
storageImpl: null,
98105
disabled: false,
99-
storagePrefix: 'DS.' + definition.name
106+
storagePrefix: 'DS.' + def.name
100107
});
101108

102-
this.store[definition.name] = {
109+
if (def.methods) {
110+
def.factory = function () {
111+
};
112+
this.utils.deepMixIn(def.factory.prototype, def.methods);
113+
}
114+
115+
this.store[def.name] = {
103116
collection: [],
104117
completedQueries: {},
105118
pendingQueries: {},

src/datastore/sync_methods/inject.js

+8-2
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.factory ? new definition.factory() : {};
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

+28
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,32 @@ 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.factory);
111+
assert.equal(lifecycle.beforeInject.callCount, 1, 'beforeInject should have been called');
112+
assert.equal(lifecycle.afterInject.callCount, 1, 'afterInject should have been called');
113+
});
86114
});

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

+3
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)