Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds async option for find method - #19 #53

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ It's not clear to me if this means that is overrides in both cases. I.e. if the default is true and you set false here, it overrides, is that right? And if the default is false but you set true here, then it also overrides? If that's the case, then the code should probably need to check for typeof relationOptions.async !== 'boolean' as well as simple truthy/falsy, since it will be falsy in the case where it's not defined at all.


### db.rel.del(type, object)

Deletes the given object. Returns a Promise.
Expand Down
107 changes: 55 additions & 52 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
137 changes: 137 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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([
{
Expand Down Expand Up @@ -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([
{
Expand Down