diff --git a/lib/route.js b/lib/route.js index b6de20884..614267056 100644 --- a/lib/route.js +++ b/lib/route.js @@ -544,32 +544,67 @@ function route(name, model, inflect) { return adapter.findMany(model, ids); } - function appendLinked(body, resources, schema, inclusions, req, res) { - var promises = [], - _this = this; + function getLinked(fetchedIds, resources, schema, key, req, res) { + var ids = linkedIds(resources, key); + if (ids.length > 0) { + var type = _.isArray(schema[key]) ? schema[key][0] : schema[key]; + type = _.isPlainObject(type) ? type.ref : type; - inclusions = _.sortBy(inclusions, function(path) { return path.length; }); + fetchedIds[type] = fetchedIds[type] || []; + ids = _.without(ids, fetchedIds[type]); + fetchedIds[type] = fetchedIds[type].concat(ids); - _.each(inclusions, function(path) { + return findResources.call(this,type,ids).then(function(resources) { + return RSVP.all(resources.map(function(resource) { + return afterTransform(inflect.singularize(type), resource, req, res); + })).then(function() { + return {type: inflect.pluralize(type), resources: resources}; + }); + }); + } - var ids = linkedIds(resources, path); - if (ids.length > 0) { - var type = _.isArray(schema[path]) ? schema[path][0] : schema[path]; - type = _.isPlainObject(type) ? type.ref : type; - promises.push(findResources.call(_this,type,ids).then(function(resources) { - body.linked = body.linked || {}; - body.linked[inflect.pluralize(type)] = resources; - return RSVP.all(resources.map(function(resource) { - return afterTransform(inflect.singularize(type), resource, req, res); - })); - })); - } - }); + var deferred = RSVP.defer(); + deferred.resolve(); + return deferred.promise; + } - return RSVP.all(promises) - .then(function() { - return body; + function appendLinked(body, resources, schema, inclusions, req, res) { + // build of tree of paths to fetch and maybe include + var includePaths = {}, + _this = this; + _.each(inclusions, function(include) { + include = include.split('.'); + var location = includePaths; + _.each(include, function(part) { + if (!location[part]) { + location[part] = {__includeInBody: false}; + } + location = location[part]; }); + location.__includeInBody = true; + }); + var fetchedIds = {}; + body.linked = {}; + + function fetchChildren(config, resources, schema, req, res) { + return RSVP.all(_.map(_.keys(config), function(key) { + if (key !== "__includeInBody") { + return getLinked.call(_this,fetchedIds, resources, schema, key, req, res) + .then(function(result) { + if (result) { + if (config[key].__includeInBody) { + body.linked[result.type] = body.linked[result.type] || []; + body.linked[result.type] = body.linked[result.type].concat(result.resources); + } + return fetchChildren(config[key], result.resources, _this._schema[inflect.singularize(result.type)], req, res); + } + }); + } + })); + } + return fetchChildren(includePaths, resources, schema, req, res).then(function() { + return body; + }); } /* @@ -582,7 +617,6 @@ function route(name, model, inflect) { function appendLinks(body, req, res) { var schemas = this._schema, _this = this; - var promises = []; _.each(body, function(value, key) { @@ -591,8 +625,9 @@ function route(name, model, inflect) { , schema = schemas[modelName]; if (schema) { addLinksToBody.call(_this, body, schema, key); - if (req.query.include) + if (req.query.include) { promises.push(appendLinked.call(_this,body, body[key], schema, req.query.include.split(','), req, res)); + } } }); diff --git a/test/all.js b/test/all.js index 437d4e856..5a891e2ab 100644 --- a/test/all.js +++ b/test/all.js @@ -359,7 +359,7 @@ _.each(global.adapters, function(port, adapter) { }); }); - it("should return immediate child documents of people by default", function(done) { + it("should return immediate child documents of people when requested", function(done) { new RSVP.Promise(function(resolve) { request(baseUrl) .put('/people/' + ids.people[0]) @@ -397,6 +397,84 @@ _.each(global.adapters, function(port, adapter) { }); }); }); + + it("should return grandchild plus child documents of people when requested", function(done) { + new RSVP.Promise(function(resolve) { + request(baseUrl) + .put('/people/' + ids.people[1]) + .send({people: [{ + links: { + pets: [ids.pets[1]] + } + }]}) + .expect('Content-Type', /json/) + .expect(200) + .end(function(error, response) { + should.not.exist(error); + var body = JSON.parse(response.text); + (body.people[0].links.pets).should.includeEql(ids.pets[1]); + resolve(); + }); + }) + .then(function() { + request(baseUrl) + .get('/people/' + ids.people[0] + '?include=pets,soulmate,soulmate.pets') + .expect('Content-Type', /json/) + .expect(200) + .end(function(error, response) { + should.not.exist(error); + var body = JSON.parse(response.text); + body.linked.pets.length.should.equal(2); + body.linked.pets[0].id.should.equal(ids.pets[0]); + body.linked.pets[0].name.should.equal(fixtures.pets[0].name); + body.linked.pets[1].id.should.equal(ids.pets[1]); + body.linked.pets[1].name.should.equal(fixtures.pets[1].name); + body.linked.people.length.should.equal(1); + body.linked.people[0].name.should.equal(fixtures.people[1].name); + body.people[0].nickname.should.equal('Super ' + fixtures.people[0].name); + body.linked.people[0].nickname.should.equal('Super ' + fixtures.people[1].name); + done(); + }); + }); + }); + + it("should return grandchild without child documents of people when requested", function(done) { + new RSVP.Promise(function(resolve) { + request(baseUrl) + .put('/people/' + ids.people[1]) + .send({people: [{ + links: { + pets: [ids.pets[1]] + } + }]}) + .expect('Content-Type', /json/) + .expect(200) + .end(function(error, response) { + should.not.exist(error); + var body = JSON.parse(response.text); + (body.people[0].links.pets).should.includeEql(ids.pets[1]); + resolve(); + }); + }) + .then(function() { + request(baseUrl) + .get('/people/' + ids.people[0] + '?include=pets,soulmate.pets') + .expect('Content-Type', /json/) + .expect(200) + .end(function(error, response) { + should.not.exist(error); + var body = JSON.parse(response.text); + body.linked.pets.length.should.equal(2); + body.linked.pets[0].id.should.equal(ids.pets[0]); + body.linked.pets[0].name.should.equal(fixtures.pets[0].name); + body.linked.pets[1].id.should.equal(ids.pets[1]); + body.linked.pets[1].name.should.equal(fixtures.pets[1].name); + should.not.exist(body.linked.people); + done(); + }); + }); + }); + }); after(function(done) {