diff --git a/lib/route.js b/lib/route.js index 5fbfd4061..262024205 100644 --- a/lib/route.js +++ b/lib/route.js @@ -661,6 +661,158 @@ function route(name, model, routeOptions) { * @return {Object} */ + function linkedIds(resources, path) { + var ids = []; + _.each(resources, function(resource) { + if (resource.links && resource.links[path]) { + var id = resource.links[path]; + if (_.isArray(id)) { + ids = ids.concat(id); + } else { + ids.push(id); + } + } + }); + return ids; + } + + function findResources(type, config, ids, req, res) { + var resourceName = inflect.pluralize(type); + + if (!config.baseUri) { + var adapter = this.adapter, + modelName = inflect.singularize(type), + model = adapter.model(modelName); + + return adapter.findMany(model, ids) + .then(function(resources) { + return RSVP.all(_.map(resources, function(resource) { + //Make req,res optionally used; if not present, skip the after transform. + if(req && res){ + return afterTransform(modelName, resource, req, res); + }else{ + var deferred = RSVP.defer(); + deferred.resolve(resource); + return deferred.promise; + } + })); + }) + .then(function(resources) { + return _.set({}, resourceName, resources); + }); + } else { + // the related resource is defined on another domain + // fetch with an http call with inclusion of the deep linked resources for this resource + return $http(config.baseUri + '/' + resourceName + '?id='+ids.join(',') + '&include='+config.remoteIncludes, {json: true}) + .spread(function (response, body){ + // get results for the primary resource + var primary = _.set({}, resourceName, body[resourceName]); + return _.reduce(body.linked, function(accum, val, key) { + // accumulate results for the linked resources + return _.set(accum, key, val); + }, primary); + }); + } + } + + function getLinked(fetchedIds, resources, schema, config, key, req, res) { + var ids = linkedIds(resources, key); + if (ids.length > 0) { + + var type = config.refs[key].type; + + fetchedIds[type] = fetchedIds[type] || []; + ids = _.without(ids, fetchedIds[type]); + fetchedIds[type] = fetchedIds[type].concat(ids); + + return findResources.call(this,type, config.refs[key], ids); + } + + var deferred = RSVP.defer(); + deferred.resolve(); + return deferred.promise; + } + + function fetchChildren(context,fetchedIds,body,config, resources, schema, req, res) { + var _this=context; + return RSVP.all(_.map(_.keys(config?config.refs:[]), function(key) { + if (key !=='refs') { + return getLinked.call(_this, fetchedIds, resources, schema, config, key, req, res) + .then(function (result) { + if (result) { + _.forEach(_.keys(result), function (resourceName) { + body.linked[resourceName] = body.linked[resourceName] || []; + body.linked[resourceName] = body.linked[resourceName].concat(result[resourceName]); + }); + return fetchChildren(context, fetchedIds, body, config[key], result[config.refs[key]], _this._schema[inflect.singularize(config.refs[key].type)], req, res); + } + }); + } + })); + } + + //BEGIN FlyVector Port: + /* + * Append a top level "linked" object for hypermedia. + * @param {Object} body deserialized response body + * * @param {Object} body deserialized response body + + * @return {Object} body + */ + + function appendLinked(body, inclusions, req, res) { + var schemas = this._schema; + var _this = this; + var promises = []; + + _.each(body, function(value, key) { + if (key === 'meta' || key === 'links') return; + var modelName = inflect.singularize(key); + var schema = schemas[modelName]; + if (schema && inclusions) { + var resources = body[key]; + var fetchedIds = {}; + body.linked = {}; + + // build of tree of paths to fetch and maybe include + var includePaths = {}; + _.forEach(inclusions, function(include) { + var splitInclude = include.split('.'); + var location = includePaths; + _.forEach(splitInclude, function(part) { + + location.refs = location.refs || {}; + + var type = _.isArray(schema[part]) ? schema[part][0] : schema[part]; + var baseUri = _.isPlainObject(type) ? type.baseUri : null; + type = _.isPlainObject(type) ? type.ref : type; + + if (!location.refs[part]) { + + location.refs[part] = {type: type}; + if (baseUri) { + var remoteIncludes = _.drop(splitInclude, _.indexOf(splitInclude, part) + 1).join('.'); + location.refs[part] = _.merge(location.refs[part], {baseUri: baseUri, remoteIncludes: remoteIncludes}); + location = location.refs[part]; + return false; + } + } + location = location.refs[part]; + }); + }); + + promises.push(fetchChildren(_this,fetchedIds,body,includePaths, resources, schema, req, res).then(function() { + return body; + })); + } + + }); + return RSVP.all(promises).then(function() { + return body; + }); + } + this.appendLinked = appendLinked; + function appendLinkForKey(body, key) { var schema = _this._schema[options.inflect ? inflect.singularize(key) : key]; var associations = getAssociations.call(_this, schema); diff --git a/test/events-reader.spec.js b/test/events-reader.spec.js index 6f41743bd..03edbcb0f 100644 --- a/test/events-reader.spec.js +++ b/test/events-reader.spec.js @@ -47,7 +47,7 @@ describe('onChange callback, event capture and at-least-once delivery semantics' before(function (done) { var that = this; - that.timeout(100000); + that.timeout(30000); harvesterApp = harvester(harvesterOptions).resource('post', { title: String @@ -100,7 +100,7 @@ describe('onChange callback, event capture and at-least-once delivery semantics' beforeEach(function (done) { var that = this; - that.timeout(100000); + that.timeout(30000); createReportResponseDfd = RSVP.defer(); createReportPromise = createReportResponseDfd.promise; @@ -142,7 +142,7 @@ describe('onChange callback, event capture and at-least-once delivery semantics' it('should skip as there is only a change handler fn defined on delete', function (done) { var that = this; - that.timeout(100000); + that.timeout(30000); that.eventsReader.skip = function (dfd, doc) { if (doc.ns === 'testDB.posts') { @@ -204,7 +204,7 @@ describe('onChange callback, event capture and at-least-once delivery semantics' function test(done, mockReports) { var that = this; - that.timeout(100000); + that.timeout(30000); mockReports(); diff --git a/test/remoteIncludes.spec.js b/test/remoteIncludes.spec.js index 2ecf29ca9..a0d3e60ab 100644 --- a/test/remoteIncludes.spec.js +++ b/test/remoteIncludes.spec.js @@ -28,7 +28,7 @@ describe('remote link', function () { before(function () { var that = this; - that.timeout(100000); + this.timeout(30000); that.harvesterApp1 = diff --git a/test/sse.spec.js b/test/sse.spec.js index 7a09e54a8..5ad63343f 100644 --- a/test/sse.spec.js +++ b/test/sse.spec.js @@ -13,7 +13,7 @@ describe('EventSource implementation for resource changes', function () { var harvesterApp; describe('Server Sent Events', function () { - this.timeout(100000); + this.timeout(30000); var lastEventId; before(function () {