Merge pull request #75 from cult-of-coders/feature/promisify-queries

[RFC] added support for promises and sync, added autoremove support from inversedSide + other small ones
This commit is contained in:
Theodor Diaconu 2016-11-29 11:25:19 +02:00 committed by GitHub
commit 82be9fc76e
11 changed files with 175 additions and 27 deletions

View file

@ -1,3 +1,8 @@
## 1.2.5
- Support for promises via .fetchSync and .fetchOneSync for client-side queries
- Support for autoremove from inverse side as well
- Fixed .fetchOne from client-side Query
## 1.2.4
- Fixed #55, #60, #61, #66
- Added Reducers Concept

View file

@ -133,22 +133,28 @@ export default class Exposure {
const config = this.config;
const getTransformedBody = this.getTransformedBody.bind(this);
const methodBody = function(body) {
if (!config.blocking) {
this.unblock();
}
let transformedBody = getTransformedBody(body);
const rootNode = createGraph(collection, transformedBody);
enforceMaxDepth(rootNode, config.maxDepth);
restrictLinks(rootNode, this.userId);
// if there is no exposure body defined, then we need to apply firewalls
return hypernova(rootNode, this.userId, {
bypassFirewalls: !!config.body
});
};
Meteor.methods({
[this.name](body) {
if (!config.blocking) {
this.unblock();
}
let transformedBody = getTransformedBody(body);
const rootNode = createGraph(collection, transformedBody);
enforceMaxDepth(rootNode, config.maxDepth);
restrictLinks(rootNode, this.userId);
return hypernova(rootNode, this.userId, {
bypassFirewalls: !!config.body
});
[this.name]: methodBody,
[this.name + '_async']: async function(...args) {
return methodBody.call(this, ...args);
}
});
}

View file

@ -61,6 +61,10 @@ _.extend(Mongo.Collection.prototype, {
} else {
object = {_id: objectOrId};
}
if (!object) {
throw new Meteor.Error(`We could not find any object with _id: "${objectOrId}" within the collection: ${this._name}`);
}
}
return linkData[name].createLink(object);

View file

@ -24,12 +24,18 @@ export default class Linker {
this._validateAndClean();
this._extendSchema();
// if it's a virtual field make sure that when this is deleted, it will be removed from the references
// initialize cascade removal hooks.
if (linkConfig.autoremove) {
this._initAutoremove();
}
if (this.isVirtual()) {
this._handleReferenceRemovalForVirtualLinks();
// if it's a virtual field make sure that when this is deleted, it will be removed from the references
if (!linkConfig.autoremove) {
this._handleReferenceRemovalForVirtualLinks();
}
} else {
this._initIndex();
this._initAutoremove();
}
}
@ -343,7 +349,7 @@ export default class Linker {
}
_initAutoremove() {
if (this.linkConfig.autoremove) {
if (!this.isVirtual()) {
this.mainCollection.after.remove((userId, doc) => {
this.getLinkedCollection().remove({
_id: {
@ -351,6 +357,15 @@ export default class Linker {
}
})
})
} else {
this.mainCollection.after.remove((userId, doc) => {
const linker = this.mainCollection.getLink(doc, this.linkName);
const ids = linker.find({}, {fields: {_id: 1}}).fetch().map(item => item._id);
this.getLinkedCollection().remove({
_id: {$in: ids}
})
})
}
}
}

View file

@ -36,6 +36,11 @@ PostCollection.addLinks({
field: 'autoRemoveIds',
autoremove: true
},
'autoRemovingSelfComments': {
type: '*',
collection: CommentCollection,
field: 'autoRemovingSelfCommentsIds',
},
'metaComments': {
type: '*',
collection: CommentCollection,
@ -69,6 +74,11 @@ CommentCollection.addLinks({
collection: PostCollection,
inversedBy: 'comments'
},
autoRemovePosts: {
collection: PostCollection,
inversedBy: 'autoRemovingSelfComments',
autoremove: true
},
metaPost: {
collection: PostCollection,
inversedBy: 'metaComments'
@ -344,5 +354,34 @@ describe('Collection Links', function () {
assert.equal(e.error, 'not-found');
done();
}
});
it('Should work with autoremoval from inversed and direct link', function () {
// autoremoval from direct side
let postId = PostCollection.insert({text: 'autoremove'});
const postAutoRemoveCommentsLink = PostCollection.getLink(postId, 'autoRemoveComments');
postAutoRemoveCommentsLink.add({text: 'hello'});
assert.lengthOf(postAutoRemoveCommentsLink.find().fetch(), 1);
let commentId = postAutoRemoveCommentsLink.find().fetch()[0]._id;
assert.isObject(CommentCollection.findOne(commentId));
PostCollection.remove(postId);
assert.isUndefined(CommentCollection.findOne(commentId));
// now from inversed side
commentId = CommentCollection.insert({text: 'autoremove'});
const commentAutoRemovePostsLink = CommentCollection.getLink(commentId, 'autoRemovePosts');
commentAutoRemovePostsLink.add({text: 'Hello'});
assert.lengthOf(commentAutoRemovePostsLink.find().fetch(), 1);
postId = commentAutoRemovePostsLink.find().fetch()[0]._id;
assert.isObject(PostCollection.findOne(postId));
CommentCollection.remove(commentId);
assert.isUndefined(PostCollection.findOne(postId));
})
});

View file

@ -1,6 +1,8 @@
import createGraph from '../query/lib/createGraph.js';
import recursiveFetch from '../query/lib/recursiveFetch.js';
import prepareForProcess from '../query/lib/prepareForProcess.js';
import { _ } from 'meteor/underscore';
import callWithPromise from '../query/lib/callWithPromise';
import Base from './namedQuery.base';
export default class extends Base {
@ -31,6 +33,26 @@ export default class extends Base {
this.subscriptionHandle = null;
}
/**
* Fetches elements in sync using promises
* @return {*}
*/
async fetchSync() {
if (this.subscriptionHandle) {
throw new Meteor.Error('This query is reactive, meaning you cannot use promises to fetch the data.');
}
return await callWithPromise(this.name + '_async', prepareForProcess(this.body, this.params));
}
/**
* Fetches one element in sync
* @return {*}
*/
async fetchOneSync() {
return _.first(await this.fetchSync())
}
/**
* Retrieves the data.
* @param callbackOrOptions

View file

@ -0,0 +1,9 @@
export default (method, myParameters) => {
return new Promise((resolve, reject) => {
Meteor.call(method, myParameters, (err, res) => {
if (err) reject(err.reason || 'Something went wrong.');
resolve(res);
});
});
};

View file

@ -1,7 +1,8 @@
import { _ } from 'meteor/underscore';
import createGraph from './lib/createGraph.js';
import recursiveFetch from './lib/recursiveFetch.js';
import prepareForProcess from './lib/prepareForProcess.js';
import deepClone from './lib/deepClone.js';
import callWithPromise from './lib/callWithPromise';
import Base from './query.base';
export default class Query extends Base {
@ -32,6 +33,26 @@ export default class Query extends Base {
this.subscriptionHandle = null;
}
/**
* Fetches elements in sync using promises
* @return {*}
*/
async fetchSync() {
if (this.subscriptionHandle) {
throw new Meteor.Error('This query is reactive, meaning you cannot use promises to fetch the data.');
}
return await callWithPromise(this.name + '_async', prepareForProcess(this.body, this.params));
}
/**
* Fetches one element in sync
* @return {*}
*/
async fetchOneSync() {
return _.first(await this.fetchSync())
}
/**
* Retrieves the data.
* @param callbackOrOptions
@ -45,6 +66,14 @@ export default class Query extends Base {
}
}
/**
* @param args
* @returns {*}
*/
fetchOne(...args) {
return _.first(this.fetch(...args));
}
/**
* Gets the count of matching elements.
* @param callback

View file

@ -186,4 +186,29 @@ describe('Query Client Tests', function () {
}
});
});
it('Should work with promises', async function () {
let query = createQuery({
groups: {
posts: {
title: 1
}
}
});
let result = await query.fetchSync();
assert.isArray(result);
assert.isTrue(result.length > 0);
result.forEach(item => {
assert.isArray(item.posts);
assert.isTrue(item.posts.length > 0);
});
result = await query.fetchOneSync();
assert.isObject(result);
assert.isString(result._id);
assert.isArray(result.posts);
})
});

View file

@ -5,12 +5,6 @@ import './lib/query/reducers/extension.js';
import './lib/namedQuery/expose/extension.js';
import './lib/namedQuery/extension.js';
import { checkNpmVersions } from 'meteor/tmeasday:check-npm-versions';
checkNpmVersions({
'sift': '3.2.x'
}, 'cultofcoders:grapher');
export {
default as createQuery
} from './lib/query/createQuery.js';

View file

@ -1,6 +1,6 @@
Package.describe({
name: 'cultofcoders:grapher',
version: '1.2.4',
version: '1.2.5',
// Brief, one-line summary of the package.
summary: 'Grapher makes linking collections easily. And fetching data as a graph.',
// URL to the Git repository containing the source code for this package.