Skip to content

Commit

Permalink
Closes #14.
Browse files Browse the repository at this point in the history
Closes #59.
Closes #61.
Closes #62.
  • Loading branch information
jmdobry committed May 18, 2014
1 parent 0b4e109 commit 87a9652
Show file tree
Hide file tree
Showing 29 changed files with 1,347 additions and 329 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
##### 0.9.0 - xx May 2014

###### Breading API changes
- #61 - Make custom serializers/deserializers more valuable
- #59, #62 - Make queryTransform() consistent with the rest of the API

###### Backwards compatible API changes
- #30, #48 - DSCacheFactory integration
- #49 - DS.bindOne($scope, prop, resourceName, id)
Expand Down
645 changes: 490 additions & 155 deletions dist/angular-data.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/angular-data.min.js

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions guide/angular-data/resource/resource.doc
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@ The following asynchronous operations support a model lifecycle:
- `destroy` - Implementation provided by adapter
- `afterDestroy` - Default: `noop`

### Additional hooks

- `serialize` - Default: `noop` - Called before data is sent through adapters.
- `deserialize` - Default: `noop` - Called after data is returned from adapters.
- `beforeInject` - Default: `noop` - Called before data is injected into the data store.
- `afterInject` - Default: `noop` - Called after data is injected into the data store.

See the [DSProvider.defaults API](/documentation/api/angular-data/DSProvider.properties:defaults) for detailed information.

### Define lifecycle hooks
All lifecycle hooks will be executed according to the following signature:
```js
Expand Down
25 changes: 22 additions & 3 deletions karma.start.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Setup global test variables
var $rootScope, $q, $log, DSProvider, DSHttpAdapterProvider, DS, app, $httpBackend, p1, p2, p3, p4, p5;
var $rootScope, $q, $log, DSProvider, DSHttpAdapterProvider, DS, DSHttpAdapter, app, $httpBackend, p1, p2, p3, p4, p5;

var lifecycle = {};

Expand Down Expand Up @@ -73,9 +73,22 @@ beforeEach(function (done) {
lifecycle.afterInject = function () {
lifecycle.afterInject.callCount += 1;
};
lifecycle.serialize = function (resourceName, data) {
lifecycle.serialize.callCount += 1;
return data;
};
lifecycle.deserialize = function (resourceName, data) {
lifecycle.deserialize.callCount += 1;
return data.data;
};
lifecycle.queryTransform = function (resourceName, query) {
lifecycle.queryTransform.callCount += 1;
return query;
};
module('app', function (_DSProvider_, _DSHttpAdapterProvider_) {
DSProvider = _DSProvider_;
DSHttpAdapterProvider = _DSHttpAdapterProvider_;
DSHttpAdapterProvider.defaults.queryTransform = lifecycle.queryTransform;
DSProvider = _DSProvider_;
DSProvider.defaults.baseUrl = 'http://test.angular-cache.com';
DSProvider.defaults.beforeValidate = lifecycle.beforeValidate;
DSProvider.defaults.validate = lifecycle.validate;
Expand All @@ -88,12 +101,15 @@ beforeEach(function (done) {
DSProvider.defaults.afterDestroy = lifecycle.afterDestroy;
DSProvider.defaults.beforeInject = lifecycle.beforeInject;
DSProvider.defaults.afterInject = lifecycle.afterInject;
DSProvider.defaults.serialize = lifecycle.serialize;
DSProvider.defaults.deserialize = lifecycle.deserialize;
});
inject(function (_$rootScope_, _$q_, _$httpBackend_, _DS_, _$log_) {
inject(function (_$rootScope_, _$q_, _$httpBackend_, _DS_, _$log_, _DSHttpAdapter_) {
// Setup global mocks
$q = _$q_;
$rootScope = _$rootScope_;
DS = _DS_;
DSHttpAdapter = _DSHttpAdapter_;
$httpBackend = _$httpBackend_;
DS.defineResource({
name: 'post',
Expand All @@ -112,6 +128,9 @@ beforeEach(function (done) {
lifecycle.afterDestroy.callCount = 0;
lifecycle.beforeInject.callCount = 0;
lifecycle.afterInject.callCount = 0;
lifecycle.serialize.callCount = 0;
lifecycle.deserialize.callCount = 0;
lifecycle.queryTransform.callCount = 0;

p1 = { author: 'John', age: 30, id: 5 };
p2 = { author: 'Sally', age: 31, id: 6 };
Expand Down
114 changes: 34 additions & 80 deletions src/adapters/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,68 +14,21 @@ function DSHttpAdapterProvider() {
*
* Properties:
*
* - `{function}` - `serialize` - See [the guide](/documentation/guide/adapters/index). Default: No-op.
* - `{function}` - `deserialize` - See [the guide](/documentation/guide/adapters/index). Default: No-op.
* - `{function}` - `queryTransform` - See [the guide](/documentation/guide/adapters/index). Default: No-op.
*/
var defaults = this.defaults = {
/**
* @doc property
* @id DSHttpAdapterProvider.properties:defaults.serialize
* @name defaults.serialize
* @description
* Your server might expect a custom request object rather than the plain POJO payload. Use `serialize` to
* create your custom request object.
*
* ## Example:
* ```js
* DSHttpAdapterProvider.defaults.serialize = function (data) {
* return {
* payload: data
* };
* };
* ```
*
* @param {object} data Data to be sent to the server.
* @returns {*} Returns `data` as-is.
*/
serialize: function (data) {
return data;
},

/**
* @doc property
* @id DSHttpAdapterProvider.properties:defaults.deserialize
* @name defaults.deserialize
* @description
* Your server might return a custom response object instead of the plain POJO payload. Use `deserialize` to
* pull the payload out of your response object so angular-data can use it.
*
* ## Example:
* ```js
* DSHttpAdapterProvider.defaults.deserialize = function (data) {
* return data ? data.payload : data;
* };
* ```
*
* @param {object} data Response object from `$http()`.
* @returns {*} Returns `data.data`.
*/
deserialize: function (data) {
return data.data;
},

/**
* @doc property
* @id DSHttpAdapterProvider.properties:defaults.queryTransform
* @name defaults.queryTransform
* @description
* Transform the angular-data query to something your server understands. You might just do this on the server instead.
*
* @param {string} resourceName The name of the resource.
* @param {object} query Response object from `$http()`.
* @returns {*} Returns `query` as-is.
*/
queryTransform: function (query) {
queryTransform: function (resourceName, query) {
return query;
}
};
Expand Down Expand Up @@ -361,7 +314,7 @@ function DSHttpAdapterProvider() {

return $http(config).then(function (data) {
$log.debug(data.config.method + ' request:' + data.config.url + ' Time taken: ' + (new Date().getTime() - start) + 'ms', arguments);
return defaults.deserialize(data);
return data;
});
}

Expand Down Expand Up @@ -399,75 +352,76 @@ function DSHttpAdapterProvider() {
}));
}

function find(resourceConfig, id, options) {
function create(resourceConfig, attrs, options) {
options = options || {};
return this.GET(
DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id),
return this.POST(
DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint),
attrs,
options
);
}

function findAll(resourceConfig, params, options) {
function destroy(resourceConfig, id, options) {
options = options || {};
options.params = options.params || {};
if (options.params.query) {
options.params.query = defaults.queryTransform(options.params.query);
}
DSUtils.deepMixIn(options, params);
return this.GET(
DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint),
return this.DEL(
DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id),
options
);
}

function create(resourceConfig, attrs, options) {
function destroyAll(resourceConfig, params, options) {
options = options || {};
return this.POST(
options.params = options.params || {};
if (params) {
params.query = params.query ? defaults.queryTransform(resourceConfig.name, params.query) : params.query;
DSUtils.deepMixIn(options.params, params);
}
return this.DEL(
DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint),
defaults.serialize(attrs),
options
);
}

function update(resourceConfig, id, attrs, options) {
return this.PUT(
function find(resourceConfig, id, options) {
options = options || {};
return this.GET(
DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id),
defaults.serialize(attrs),
options
);
}

function updateAll(resourceConfig, attrs, params, options) {
function findAll(resourceConfig, params, options) {
options = options || {};
options.params = options.params || {};
if (options.params.query) {
options.params.query = defaults.queryTransform(options.params.query);
if (params) {
params.query = params.query ? defaults.queryTransform(resourceConfig.name, params.query) : params.query;
DSUtils.deepMixIn(options.params, params);
}
DSUtils.deepMixIn(options, params);
return this.PUT(
return this.GET(
DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint),
defaults.serialize(attrs),
options
);
}

function destroy(resourceConfig, id, options) {
function update(resourceConfig, id, attrs, options) {
options = options || {};
return this.DEL(
return this.PUT(
DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id),
attrs,
options
);
}

function destroyAll(resourceConfig, params, options) {
function updateAll(resourceConfig, attrs, params, options) {
options = options || {};
options.params = options.params || {};
if (options.params.query) {
options.params.query = defaults.queryTransform(options.params.query);
if (params) {
params.query = params.query ? defaults.queryTransform(resourceConfig.name, params.query) : params.query;
DSUtils.deepMixIn(options.params, params);
}
DSUtils.deepMixIn(options, params);
return this.DEL(
return this.PUT(
DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint),
attrs,
options
);
}
Expand Down
10 changes: 5 additions & 5 deletions src/datastore/async_methods/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,14 @@ function create(resourceName, attrs, options) {
return _this.$q.promisify(definition.beforeCreate)(resourceName, attrs);
})
.then(function (attrs) {
return _this.adapters[options.adapter || definition.defaultAdapter].create(definition, attrs, options);
return _this.adapters[options.adapter || definition.defaultAdapter].create(definition, definition.serialize(resourceName, attrs), options);
})
.then(function (data) {
return _this.$q.promisify(definition.afterCreate)(resourceName, data);
.then(function (res) {
return _this.$q.promisify(definition.afterCreate)(resourceName, definition.deserialize(resourceName, res));
})
.then(function (data) {
var created = _this.inject(definition.name, data),
id = created[definition.idAttribute];
var created = _this.inject(definition.name, data);
var id = created[definition.idAttribute];
resource.previousAttributes[id] = _this.utils.deepMixIn({}, created);
resource.saved[id] = _this.utils.updateTimestamp(resource.saved[id]);
return _this.get(definition.name, id);
Expand Down
6 changes: 3 additions & 3 deletions src/datastore/async_methods/destroyAll.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,16 @@ function destroyAll(resourceName, params, options) {
if (!this.definitions[resourceName]) {
deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'));
} else if (!this.utils.isObject(params)) {
deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'params: Must be an object!', { params: { actual: typeof params, expected: 'object' } }));
deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'params: Must be an object!'));
} else if (!this.utils.isObject(options)) {
deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } }));
deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!'));
} else {
try {
var definition = this.definitions[resourceName];

promise = promise
.then(function () {
return _this.adapters[options.adapter || definition.defaultAdapter].destroyAll(definition, { params: params }, options);
return _this.adapters[options.adapter || definition.defaultAdapter].destroyAll(definition, params, options);
})
.then(function () {
return _this.ejectAll(resourceName, params);
Expand Down
3 changes: 2 additions & 1 deletion src/datastore/async_methods/find.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ function find(resourceName, id, options) {
if (!(id in resource.completedQueries)) {
if (!(id in resource.pendingQueries)) {
promise = resource.pendingQueries[id] = _this.adapters[options.adapter || definition.defaultAdapter].find(definition, id, options)
.then(function (data) {
.then(function (res) {
var data = definition.deserialize(resourceName, res);
if (options.cacheResponse) {
// Query is no longer pending
delete resource.pendingQueries[id];
Expand Down
9 changes: 5 additions & 4 deletions src/datastore/async_methods/findAll.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ function _findAll(utils, resourceName, params, options) {
if (!(queryHash in resource.pendingQueries)) {

// This particular query has never even been made
resource.pendingQueries[queryHash] = _this.adapters[options.adapter || definition.defaultAdapter].findAll(definition, { params: params }, options)
.then(function (data) {
resource.pendingQueries[queryHash] = _this.adapters[options.adapter || definition.defaultAdapter].findAll(definition, params, options)
.then(function (res) {
var data = definition.deserialize(resourceName, res);
if (options.cacheResponse) {
try {
return processResults.apply(_this, [utils, data, resourceName, queryHash]);
Expand Down Expand Up @@ -132,9 +133,9 @@ function findAll(resourceName, params, options) {
if (!this.definitions[resourceName]) {
deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'));
} else if (!this.utils.isObject(params)) {
deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'params: Must be an object!', { params: { actual: typeof params, expected: 'object' } }));
deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'params: Must be an object!'));
} else if (!this.utils.isObject(options)) {
deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } }));
deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!'));
} else {
if (!('cacheResponse' in options)) {
options.cacheResponse = true;
Expand Down
6 changes: 3 additions & 3 deletions src/datastore/async_methods/save.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ function save(resourceName, id, options) {
attrs = changes;
}
}
return _this.adapters[options.adapter || definition.defaultAdapter].update(definition, id, attrs, options);
return _this.adapters[options.adapter || definition.defaultAdapter].update(definition, id, definition.serialize(resourceName, attrs), options);
})
.then(function (data) {
return _this.$q.promisify(definition.afterUpdate)(resourceName, data);
.then(function (res) {
return _this.$q.promisify(definition.afterUpdate)(resourceName, definition.deserialize(resourceName, res));
})
.then(function (data) {
_this.inject(definition.name, data, options);
Expand Down
17 changes: 9 additions & 8 deletions src/datastore/async_methods/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ var errorPrefix = 'DS.update(resourceName, id, attrs[, options]): ';
* - `{RuntimeError}`
* - `{UnhandledError}`
*/
function save(resourceName, id, attrs, options) {
function update(resourceName, id, attrs, options) {
var deferred = this.$q.defer(),
promise = deferred.promise;

Expand Down Expand Up @@ -84,17 +84,18 @@ function save(resourceName, id, attrs, options) {
return _this.$q.promisify(definition.beforeUpdate)(resourceName, attrs);
})
.then(function (attrs) {
return _this.adapters[options.adapter || definition.defaultAdapter].update(definition, id, attrs, options);
return _this.adapters[options.adapter || definition.defaultAdapter].update(definition, id, definition.serialize(resourceName, attrs), options);
})
.then(function (data) {
return _this.$q.promisify(definition.afterUpdate)(resourceName, data);
.then(function (res) {
return _this.$q.promisify(definition.afterUpdate)(resourceName, definition.deserialize(resourceName, res));
})
.then(function (data) {
if (options.cacheResponse) {
var item = _this.inject(definition.name, data, options);
resource.previousAttributes[id] = _this.utils.deepMixIn({}, data);
var updated = _this.inject(definition.name, data, options);
var id = updated[definition.idAttribute];
resource.previousAttributes[id] = _this.utils.deepMixIn({}, updated);
resource.saved[id] = _this.utils.updateTimestamp(resource.saved[id]);
return item;
return _this.get(definition.name, id);
} else {
return data;
}
Expand All @@ -105,4 +106,4 @@ function save(resourceName, id, attrs, options) {
return promise;
}

module.exports = save;
module.exports = update;
Loading

0 comments on commit 87a9652

Please sign in to comment.