diff --git a/README.md b/README.md index 5f61375..ce78542 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ Result: } ``` -If an `id` isn't found, it's simply not returned. Notice that above, there is no object with an `id` of `3`. +If an `id` isn't found, it's simply not returned. Notice that above, there is no object with an `id` of `3`. `find` results are always returned ordered by id. The order of your `ids` array will not necessarily be reflected in the returned array of objects. @@ -321,6 +321,8 @@ The following options based on the options for [PouchDB batch fetch](http://pouc * `limit`: Maximum number of documents to return. * `skip`: Number of docs to skip before returning (warning: poor performance on IndexedDB/LevelDB!). +Also it is possible to add an async option `{async: true|false}` in order to force to sideload or not dependent objects. Please refer to the [Async relationships](#async-relationships) chapter for more details. + ### db.rel.del(type, object) Deletes the given object. Returns a Promise. diff --git a/lib/index.js b/lib/index.js index 886d9d1..fe47dc9 100644 --- a/lib/index.js +++ b/lib/index.js @@ -257,67 +257,70 @@ exports.setSchema = function (schema) { foundObjects.get(type).set(JSON.stringify(obj.id), obj); - // fetch all relations var subTasks = []; - Object.keys(typeInfo.relations || {}).forEach(function (field) { - var relationDef = typeInfo.relations[field]; - var relationType = Object.keys(relationDef)[0]; - var relatedType = relationDef[relationType]; - if (typeof relatedType !== 'string') { - var relationOptions = relatedType.options; - if (relationOptions && relationOptions.async) { - return; - } - relatedType = relatedType.type; - } - if (relationType === 'belongsTo') { - var relatedId = obj[field]; - if (typeof relatedId !== 'undefined') { - subTasks.push(Promise.resolve().then(function () { - - // short-circuit if it's already in the foundObjects - // else we could get caught in an infinite loop - if (foundObjects.has(relatedType) && - foundObjects.get(relatedType).has(JSON.stringify(relatedId))) { - return; - } - - // signal that we need to fetch it - return { - relatedType: relatedType, - relatedIds: [relatedId] - }; - })); + + if (!idOrIds || !idOrIds.async) { + // fetch all relations + Object.keys(typeInfo.relations || {}).forEach(function (field) { + var relationDef = typeInfo.relations[field]; + var relationType = Object.keys(relationDef)[0]; + var relatedType = relationDef[relationType]; + if (typeof relatedType !== 'string') { + var relationOptions = relatedType.options; + if (relationOptions && relationOptions.async && typeof idOrIds === 'undefined') { + return; + } + relatedType = relatedType.type; } - } else { // hasMany - var relatedIds = extend(true, [], obj[field]); - if (typeof relatedIds !== 'undefined' && relatedIds.length) { - subTasks.push(Promise.resolve().then(function () { - - // filter out all ids that are already in the foundObjects - for (var i = relatedIds.length - 1; i >= 0; i--) { - var relatedId = relatedIds[i]; + if (relationType === 'belongsTo') { + var relatedId = obj[field]; + if (typeof relatedId !== 'undefined') { + subTasks.push(Promise.resolve().then(function () { + + // short-circuit if it's already in the foundObjects + // else we could get caught in an infinite loop if (foundObjects.has(relatedType) && foundObjects.get(relatedType).has(JSON.stringify(relatedId))) { - delete relatedIds[i]; + return; } - } - relatedIds = relatedIds.filter(function (relatedId) { - return typeof relatedId !== 'undefined'; - }); - - // just return the ids and the types. We'll find them all - // in a single bulk operation in order to minimize HTTP requests - if (relatedIds.length) { + + // signal that we need to fetch it return { relatedType: relatedType, - relatedIds: relatedIds + relatedIds: [relatedId] }; - } - })); + })); + } + } else { // hasMany + var relatedIds = extend(true, [], obj[field]); + if (typeof relatedIds !== 'undefined' && relatedIds.length) { + subTasks.push(Promise.resolve().then(function () { + + // filter out all ids that are already in the foundObjects + for (var i = relatedIds.length - 1; i >= 0; i--) { + var relatedId = relatedIds[i]; + if (foundObjects.has(relatedType) && + foundObjects.get(relatedType).has(JSON.stringify(relatedId))) { + delete relatedIds[i]; + } + } + relatedIds = relatedIds.filter(function (relatedId) { + return typeof relatedId !== 'undefined'; + }); + + // just return the ids and the types. We'll find them all + // in a single bulk operation in order to minimize HTTP requests + if (relatedIds.length) { + return { + relatedType: relatedType, + relatedIds: relatedIds + }; + } + })); + } } - } - }); + }); + } return Promise.all(subTasks); }); return Promise.all(tasks); diff --git a/test/test.js b/test/test.js index 7702a5d..079120b 100644 --- a/test/test.js +++ b/test/test.js @@ -1847,6 +1847,83 @@ function tests(dbName, dbType) { }); }); }); + + it('does sideload if async option is force to false', function () { + db.setSchema([ + { + singular: 'author', + plural: 'authors', + relations: { + books: {hasMany: {type: 'books', options: {async: true}}} + } + }, + { + singular: 'book', + plural: 'books', + relations: { + author: {belongsTo: {type: 'author', options: {async: false}}} + } + } + ]); + + return db.rel.save('author', { + name: 'Stephen King', + id: 19, + books: [1, 2, 3] + }).then(function () { + return db.rel.save('book', { + id: 1, + title: 'The Gunslinger' + }); + }).then(function () { + return db.rel.save('book', { + id: 2, + title: 'The Drawing of the Three' + }); + }).then(function () { + return db.rel.save('book', { + id: 3, + title: 'The Wastelands' + }); + }).then(function () { + return db.rel.find('author', {async: false}); + }).then(function (res) { + ['authors', 'books'].forEach(function (type) { + res[type].forEach(function (obj) { + obj.rev.should.be.a('string'); + delete obj.rev; + }); + }); + res.should.deep.equal({ + "authors": [ + { + "name": "Stephen King", + "books": [ + 1, + 2, + 3 + ], + "id": 19 + } + ], + "books": [ + { + "title": "The Gunslinger", + "id": 1 + }, + { + "title": "The Drawing of the Three", + "id": 2 + }, + { + "title": "The Wastelands", + "id": 3 + } + ] + }); + }); + }); + it('does not sideload if async option is true', function () { db.setSchema([ { @@ -1907,6 +1984,66 @@ function tests(dbName, dbType) { }); }); + it('does not sideload if async option is force to true', function () { + db.setSchema([ + { + singular: 'author', + plural: 'authors', + relations: { + books: {hasMany: 'books'} + } + }, + { + singular: 'book', + plural: 'books', + relations: { + author: {belongsTo: {type: 'author', options: {async: true}}} + } + } + ]); + + return db.rel.save('author', { + name: 'Stephen King', + id: 19, + books: [1, 2, 3] + }).then(function () { + return db.rel.save('book', { + id: 1, + title: 'The Gunslinger' + }); + }).then(function () { + return db.rel.save('book', { + id: 2, + title: 'The Drawing of the Three' + }); + }).then(function () { + return db.rel.save('book', { + id: 3, + title: 'The Wastelands' + }); + }).then(function () { + return db.rel.find('author', {async: true}); + }).then(function (res) { + res['authors'].forEach(function (obj) { + obj.rev.should.be.a('string'); + delete obj.rev; + }); + res.should.deep.equal({ + "authors": [ + { + "name": "Stephen King", + "books": [ + 1, + 2, + 3 + ], + "id": 19 + } + ] + }); + }); + }); + it('fromRawDoc works with changes', function () { db.setSchema([ {