Skip to content

Commit eab626d

Browse files
committed
Closes #96.
1 parent fedd01b commit eab626d

File tree

11 files changed

+133
-49
lines changed

11 files changed

+133
-49
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
##### Backwards compatible API changes
44
- #93 - Added `DS.createInstance(resourceName[, attrs][, options])`
5+
- #96 - Resource definitions should be able to proxy DS methods
56

67
###### Backwards compatible bug fixes
78
- #90 - DS.create isn't added to completedQueries (`DS.create` now adds a completed query entry)

dist/angular-data.js

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3184,7 +3184,7 @@ function Defaults() {
31843184

31853185
Defaults.prototype.idAttribute = 'id';
31863186
Defaults.prototype.defaultAdapter = 'DSHttpAdapter';
3187-
Defaults.prototype.filter = function (collection, resourceName, params, options) {
3187+
Defaults.prototype.defaultFilter = function (collection, resourceName, params, options) {
31883188
var _this = this;
31893189
var filtered = collection;
31903190
var where = null;
@@ -4219,6 +4219,7 @@ function Resource(utils, options) {
42194219
* - `{string=}` - `endpoint` - The attribute that specifies the primary key for this resource. Default is the value of `name`.
42204220
* - `{string=}` - `baseUrl` - The url relative to which all AJAX requests will be made.
42214221
* - `{boolean=}` - `useClass` - Whether to use a wrapper class created from the ProperCase name of the resource. The wrapper will always be used for resources that have `methods` defined.
4222+
* - `{function=}` - `defaultFilter` - Override the filtering used internally by `DS.filter` with you own function here.
42224223
* - `{*=}` - `meta` - A property reserved for developer use. This will never be used by the API.
42234224
* - `{object=}` - `methods` - If provided, items of this resource will be wrapped in a constructor function that is
42244225
* empty save for the attributes in this option which will be mixed in to the constructor function prototype. Enabling
@@ -4242,6 +4243,7 @@ function Resource(utils, options) {
42424243
*/
42434244
function defineResource(definition) {
42444245
var IA = this.errors.IA;
4246+
var DS = this;
42454247

42464248
if (this.utils.isString(definition)) {
42474249
definition = definition.replace(/\s/gi, '');
@@ -4262,19 +4264,27 @@ function defineResource(definition) {
42624264
}
42634265

42644266
try {
4265-
Resource.prototype = this.defaults;
4266-
this.definitions[definition.name] = new Resource(this.utils, definition);
4267+
// Inherit from global defaults
4268+
Resource.prototype = DS.defaults;
4269+
DS.definitions[definition.name] = new Resource(DS.utils, definition);
42674270

4268-
var _this = this;
4269-
var def = this.definitions[definition.name];
4271+
var def = DS.definitions[definition.name];
4272+
4273+
// Remove this in v0.11.0 and make a breaking change notice
4274+
// the the `filter` option has been renamed to `defaultFilter`
4275+
if (def.filter) {
4276+
def.defaultFilter = def.filter;
4277+
delete def.filter;
4278+
}
42704279

4271-
var cache = this.cacheFactory('DS.' + def.name, {
4280+
// Setup the cache
4281+
var cache = DS.cacheFactory('DS.' + def.name, {
42724282
maxAge: def.maxAge || null,
42734283
recycleFreq: def.recycleFreq || 1000,
42744284
cacheFlushInterval: def.cacheFlushInterval || null,
42754285
deleteOnExpire: def.deleteOnExpire || 'none',
42764286
onExpire: function (id) {
4277-
_this.eject(def.name, id);
4287+
DS.eject(def.name, id);
42784288
},
42794289
capacity: Number.MAX_VALUE,
42804290
storageMode: 'memory',
@@ -4283,22 +4293,25 @@ function defineResource(definition) {
42834293
storagePrefix: 'DS.' + def.name
42844294
});
42854295

4296+
// Create the wrapper class for the new resource
42864297
def.class = definition.name[0].toUpperCase() + definition.name.substring(1);
42874298
eval('function ' + def.class + '() {}');
42884299
def[def.class] = eval(def.class);
42894300

4301+
// Apply developer-defined methods
42904302
if (def.methods) {
4291-
this.utils.deepMixIn(def[def.class].prototype, def.methods);
4303+
DS.utils.deepMixIn(def[def.class].prototype, def.methods);
42924304
}
42934305

4306+
// Prepare for computed properties
42944307
if (def.computed) {
4295-
this.utils.forOwn(def.computed, function (fn, field) {
4308+
DS.utils.forOwn(def.computed, function (fn, field) {
42964309
if (def.methods && field in def.methods) {
4297-
_this.$log.warn(errorPrefix + 'Computed property "' + field + '" conflicts with previously defined prototype method!');
4310+
DS.$log.warn(errorPrefix + 'Computed property "' + field + '" conflicts with previously defined prototype method!');
42984311
}
42994312
var match = fn.toString().match(/function.*?\(([\s\S]*?)\)/);
43004313
var deps = match[1].split(',');
4301-
fn.deps = _this.utils.filter(deps, function (dep) {
4314+
fn.deps = DS.utils.filter(deps, function (dep) {
43024315
return !!dep;
43034316
});
43044317
angular.forEach(fn.deps, function (val, index) {
@@ -4307,7 +4320,8 @@ function defineResource(definition) {
43074320
});
43084321
}
43094322

4310-
this.store[def.name] = {
4323+
// Initialize store data for the new resource
4324+
DS.store[def.name] = {
43114325
collection: [],
43124326
completedQueries: {},
43134327
pendingQueries: {},
@@ -4318,9 +4332,27 @@ function defineResource(definition) {
43184332
observers: {},
43194333
collectionModified: 0
43204334
};
4321-
}
4322-
catch
4323-
(err) {
4335+
4336+
// Proxy DS methods with shorthand ones
4337+
DS.utils.forOwn(DS, function (func, name) {
4338+
if (angular.isFunction(func) && func.toString().indexOf('(resourceName, ') !== -1) {
4339+
def[name] = function () {
4340+
var args = Array.prototype.slice.call(arguments);
4341+
args.unshift(def.name);
4342+
return func.apply(DS, args);
4343+
};
4344+
} else if (name === 'bindOne' || name === 'bindAll') {
4345+
def[name] = function () {
4346+
var args = Array.prototype.slice.call(arguments);
4347+
args.splice(2, 0, def.name);
4348+
return DS[name].apply(DS, args);
4349+
};
4350+
}
4351+
});
4352+
4353+
return def;
4354+
} catch (err) {
4355+
DS.$log.error(err);
43244356
delete this.definitions[definition.name];
43254357
delete this.store[definition.name];
43264358
throw err;
@@ -4626,7 +4658,7 @@ function filter(resourceName, params, options) {
46264658
}
46274659
}
46284660

4629-
return definition.filter.call(this, resource.collection, resourceName, params, options);
4661+
return definition.defaultFilter.call(this, resource.collection, resourceName, params, options);
46304662
}
46314663

46324664
module.exports = filter;

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.

guide/angular-data/queries.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ Here's how to replace Angular-data's filter:
6767

6868
```js
6969
// override how DS.filter handles the "where", "skip", "limit" and "orderBy" clauses
70-
DSProvider.defaults.filter = function (collection, resourceName, params, options) {
70+
DSProvider.defaults.defaultFilter = function (collection, resourceName, params, options) {
7171
// examine params and
7272
// decide whether to exclude items, skip items, start from an offset, or sort the items
7373

@@ -90,7 +90,7 @@ You can even override the filter per-resource:
9090
```js
9191
DS.defineResource({
9292
name: 'post',
93-
filter: function (collection, resourceName, params, options) {
93+
defaultFilter: function (collection, resourceName, params, options) {
9494
//
9595
}
9696
});

guide/angular-data/resource/resource.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ about a particular _resource_, like what its root endpoint is and which attribut
1919
resource. A _resource definition_ can also specify validation functions to be executed before create and update
2020
operations.
2121

22+
Resource definitions proxy `DS` methods that require a `resourceName` argument (a convenient shorthand!). Example:
23+
24+
```js
25+
var User = DS.defineResource('user');
26+
27+
// The following are equivalent
28+
DS.find('user', 5);
29+
User.find(5);
30+
```
31+
2232
See [defineResource(definition)](/documentation/api/api/DS.sync_methods:defineResource) for detailed API information.
2333

2434
@doc overview
@@ -29,7 +39,7 @@ See [defineResource(definition)](/documentation/api/api/DS.sync_methods:defineRe
2939
The simplest resource definition:
3040

3141
```js
32-
DS.defineResource('document');
42+
var Document = DS.defineResource('document');
3343
```
3444

3545
With this definition the data store assumes the following:
@@ -47,7 +57,7 @@ With this definition the data store assumes the following:
4757
An advanced resource definition:
4858

4959
```js
50-
DS.defineResource({
60+
var Document = DS.defineResource({
5161
name: 'document',
5262
idAttribute: '_id',
5363
endpoint: 'documents',

src/datastore/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ function Defaults() {
99

1010
Defaults.prototype.idAttribute = 'id';
1111
Defaults.prototype.defaultAdapter = 'DSHttpAdapter';
12-
Defaults.prototype.filter = function (collection, resourceName, params, options) {
12+
Defaults.prototype.defaultFilter = function (collection, resourceName, params, options) {
1313
var _this = this;
1414
var filtered = collection;
1515
var where = null;

src/datastore/sync_methods/defineResource.js

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ function Resource(utils, options) {
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.
5353
* - `{boolean=}` - `useClass` - Whether to use a wrapper class created from the ProperCase name of the resource. The wrapper will always be used for resources that have `methods` defined.
54+
* - `{function=}` - `defaultFilter` - Override the filtering used internally by `DS.filter` with you own function here.
5455
* - `{*=}` - `meta` - A property reserved for developer use. This will never be used by the API.
5556
* - `{object=}` - `methods` - If provided, items of this resource will be wrapped in a constructor function that is
5657
* empty save for the attributes in this option which will be mixed in to the constructor function prototype. Enabling
@@ -74,6 +75,7 @@ function Resource(utils, options) {
7475
*/
7576
function defineResource(definition) {
7677
var IA = this.errors.IA;
78+
var DS = this;
7779

7880
if (this.utils.isString(definition)) {
7981
definition = definition.replace(/\s/gi, '');
@@ -94,19 +96,27 @@ function defineResource(definition) {
9496
}
9597

9698
try {
97-
Resource.prototype = this.defaults;
98-
this.definitions[definition.name] = new Resource(this.utils, definition);
99+
// Inherit from global defaults
100+
Resource.prototype = DS.defaults;
101+
DS.definitions[definition.name] = new Resource(DS.utils, definition);
99102

100-
var _this = this;
101-
var def = this.definitions[definition.name];
103+
var def = DS.definitions[definition.name];
102104

103-
var cache = this.cacheFactory('DS.' + def.name, {
105+
// Remove this in v0.11.0 and make a breaking change notice
106+
// the the `filter` option has been renamed to `defaultFilter`
107+
if (def.filter) {
108+
def.defaultFilter = def.filter;
109+
delete def.filter;
110+
}
111+
112+
// Setup the cache
113+
var cache = DS.cacheFactory('DS.' + def.name, {
104114
maxAge: def.maxAge || null,
105115
recycleFreq: def.recycleFreq || 1000,
106116
cacheFlushInterval: def.cacheFlushInterval || null,
107117
deleteOnExpire: def.deleteOnExpire || 'none',
108118
onExpire: function (id) {
109-
_this.eject(def.name, id);
119+
DS.eject(def.name, id);
110120
},
111121
capacity: Number.MAX_VALUE,
112122
storageMode: 'memory',
@@ -115,22 +125,25 @@ function defineResource(definition) {
115125
storagePrefix: 'DS.' + def.name
116126
});
117127

128+
// Create the wrapper class for the new resource
118129
def.class = definition.name[0].toUpperCase() + definition.name.substring(1);
119130
eval('function ' + def.class + '() {}');
120131
def[def.class] = eval(def.class);
121132

133+
// Apply developer-defined methods
122134
if (def.methods) {
123-
this.utils.deepMixIn(def[def.class].prototype, def.methods);
135+
DS.utils.deepMixIn(def[def.class].prototype, def.methods);
124136
}
125137

138+
// Prepare for computed properties
126139
if (def.computed) {
127-
this.utils.forOwn(def.computed, function (fn, field) {
140+
DS.utils.forOwn(def.computed, function (fn, field) {
128141
if (def.methods && field in def.methods) {
129-
_this.$log.warn(errorPrefix + 'Computed property "' + field + '" conflicts with previously defined prototype method!');
142+
DS.$log.warn(errorPrefix + 'Computed property "' + field + '" conflicts with previously defined prototype method!');
130143
}
131144
var match = fn.toString().match(/function.*?\(([\s\S]*?)\)/);
132145
var deps = match[1].split(',');
133-
fn.deps = _this.utils.filter(deps, function (dep) {
146+
fn.deps = DS.utils.filter(deps, function (dep) {
134147
return !!dep;
135148
});
136149
angular.forEach(fn.deps, function (val, index) {
@@ -139,7 +152,8 @@ function defineResource(definition) {
139152
});
140153
}
141154

142-
this.store[def.name] = {
155+
// Initialize store data for the new resource
156+
DS.store[def.name] = {
143157
collection: [],
144158
completedQueries: {},
145159
pendingQueries: {},
@@ -150,9 +164,27 @@ function defineResource(definition) {
150164
observers: {},
151165
collectionModified: 0
152166
};
153-
}
154-
catch
155-
(err) {
167+
168+
// Proxy DS methods with shorthand ones
169+
DS.utils.forOwn(DS, function (func, name) {
170+
if (angular.isFunction(func) && func.toString().indexOf('(resourceName, ') !== -1) {
171+
def[name] = function () {
172+
var args = Array.prototype.slice.call(arguments);
173+
args.unshift(def.name);
174+
return func.apply(DS, args);
175+
};
176+
} else if (name === 'bindOne' || name === 'bindAll') {
177+
def[name] = function () {
178+
var args = Array.prototype.slice.call(arguments);
179+
args.splice(2, 0, def.name);
180+
return DS[name].apply(DS, args);
181+
};
182+
}
183+
});
184+
185+
return def;
186+
} catch (err) {
187+
DS.$log.error(err);
156188
delete this.definitions[definition.name];
157189
delete this.store[definition.name];
158190
throw err;

src/datastore/sync_methods/filter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ function filter(resourceName, params, options) {
7373
}
7474
}
7575

76-
return definition.filter.call(this, resource.collection, resourceName, params, options);
76+
return definition.defaultFilter.call(this, resource.collection, resourceName, params, options);
7777
}
7878

7979
module.exports = filter;

test/integration/datastore/sync_methods/bindOne.test.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,13 @@ describe('DS.bindOne(scope, expr, resourceName, id[, cb])', function () {
6464
});
6565
it('should execute a callback if given', function (done) {
6666

67+
var Post = DS.definitions.post;
6768
var cb = sinon.spy();
68-
DS.inject('post', p1);
69+
Post.inject(p1);
6970

70-
var post = DS.get('post', 5);
71+
var post = Post.get(5);
7172

72-
DS.bindOne($scope, 'post', 'post', 5, cb);
73+
Post.bindOne($scope, 'post', 5, cb);
7374

7475
$rootScope.$apply();
7576

0 commit comments

Comments
 (0)