Merge branch 'master' of github.com:cult-of-coders/grapher into scoped-publish

This commit is contained in:
Berislav 2018-09-24 08:37:10 -07:00
commit ad017073c4
11 changed files with 227 additions and 52 deletions

View file

@ -6,8 +6,31 @@ When you use reducers with a body that uses a link that should return a single r
### From 1.2 to 1.3
SimpleSchema has been completely removed and it will no longer extend your Collection's schema automatically, therefore,
if you have configured links you have to manually add them.
SimpleSchema has been completely removed and it will no longer extend your Collection's schema automatically, therefore, if you have configured links you have to manually add them.
For example the following link:
```js
Users.addLinks({
post: {
type: 'one',
collection: Posts,
field: 'postId'
}
});
```
Requires the respective field in your Collection's schema:
```js
// schema for Users
SimpleSchema({
postId: {
type: String,
optional: true
}
})
```
The `metadata` link configuration is no longer an object, but a `Boolean`

View file

@ -74,7 +74,7 @@ Posts.createQuery({
})
```
The query above will fetch as `comments` only the ones that have been approved and that are linekd with the `post`.
The query above will fetch as `comments` only the ones that have been approved and that are linked with the `post`.
The `$filter` function shares the same `params` across all collection nodes:

View file

@ -5,11 +5,14 @@ const db = new Proxy(
{},
{
get: function(obj, prop) {
if (typeof prop === 'symbol') {
return obj[prop];
}
const collection = Mongo.Collection.get(prop);
if (!collection) {
Meteor.isDevelopment &&
console.warn(`There is no collection with the name: "${prop}"`);
return obj[prop];
}
return collection;

View file

@ -77,7 +77,7 @@ export default class extends Base {
throw new Meteor.Error('This query is reactive, meaning you cannot use promises to fetch the data.');
}
return await callWithPromise(this.name, prepareForProcess(this.body, this.params));
return await callWithPromise(this.name, this.params);
}
/**
@ -129,7 +129,7 @@ export default class extends Base {
throw new Meteor.Error('This query is reactive, meaning you cannot use promises to fetch the data.');
}
return await callWithPromise(this.name + '.count', prepareForProcess(this.body, this.params));
return await callWithPromise(this.name + '.count', this.params);
}
/**

View file

@ -42,6 +42,20 @@ describe('Named Query', function() {
});
});
it('Should return proper values using query directly via import - sync', async function() {
const query = postListExposure.clone({ title: 'User Post - 3' });
const res = await query.fetchSync();
assert.isTrue(res.length > 0);
_.each(res, post => {
assert.equal(post.title, 'User Post - 3');
assert.isObject(post.author);
assert.isObject(post.group);
});
});
it('Should work with count', function(done) {
const query = postListExposure.clone({ title: 'User Post - 3' });
@ -51,6 +65,13 @@ describe('Named Query', function() {
});
});
it('Should work with count - sync', async function() {
const query = postListExposure.clone({ title: 'User Post - 3' });
const count = await query.getCountSync();
assert.equal(6, count);
});
it('Should work with reactive counts', function(done) {
const query = postListExposure.clone({ title: 'User Post - 3' });

View file

@ -61,23 +61,27 @@ export default function(childCollectionNode, aggregateResults, metaFilters) {
aggregateResult._id == result._id;
}
_.each(aggregateResults, aggregateResult => {
let parentResult = _.find(
childCollectionNode.parent.results,
result => {
return comparator(aggregateResult, result);
}
const childLinkName = childCollectionNode.linkName;
const parentResults = childCollectionNode.parent.results;
parentResults.forEach(parentResult => {
// We are now finding the data from the pipeline that is related to the _id of the parent
const eligibleAggregateResults = aggregateResults.filter(
aggregateResult => comparator(aggregateResult, parentResult)
);
if (parentResult) {
parentResult[childCollectionNode.linkName] =
aggregateResult.data;
}
_.each(aggregateResult.data, item => {
allResults.push(item);
eligibleAggregateResults.forEach(aggregateResult => {
if (Array.isArray(parentResult[childLinkName])) {
parentResult[childLinkName].push(...aggregateResult.data);
} else {
parentResult[childLinkName] = [...aggregateResult.data];
}
});
});
aggregateResults.forEach(aggregateResult => {
allResults.push(...aggregateResult.data);
});
}
childCollectionNode.results = allResults;

View file

@ -2,7 +2,11 @@ import createSearchFilters from '../../links/lib/createSearchFilters';
import cleanObjectForMetaFilters from './lib/cleanObjectForMetaFilters';
import sift from 'sift';
export default (childCollectionNode, {limit, skip, metaFilters}) => {
export default (childCollectionNode, { limit, skip, metaFilters }) => {
if (childCollectionNode.results.length === 0) {
return;
}
const parent = childCollectionNode.parent;
const linker = childCollectionNode.linker;
@ -16,29 +20,81 @@ export default (childCollectionNode, {limit, skip, metaFilters}) => {
if (isMeta && metaFilters) {
const metaFiltersTest = sift(metaFilters);
_.each(parent.results, parentResult => {
cleanObjectForMetaFilters(parentResult, fieldStorage, metaFiltersTest);
})
cleanObjectForMetaFilters(
parentResult,
fieldStorage,
metaFiltersTest
);
});
}
_.each(parent.results, result => {
let data = assembleData(childCollectionNode, result, {
fieldStorage, strategy, isSingle
const resultsByKeyId = _.groupBy(childCollectionNode.results, '_id');
if (strategy === 'one') {
parent.results.forEach(parentResult => {
if (!parentResult[fieldStorage]) {
return;
}
parentResult[childCollectionNode.linkName] = filterAssembledData(
resultsByKeyId[parentResult[fieldStorage]],
{ limit, skip }
);
});
}
result[childCollectionNode.linkName] = filterAssembledData(data, {limit, skip})
});
}
if (strategy === 'many') {
parent.results.forEach(parentResult => {
if (!parentResult[fieldStorage]) {
return;
}
function filterAssembledData(data, {limit, skip}) {
if (limit) {
let data = [];
parentResult[fieldStorage].forEach(_id => {
data.push(_.first(resultsByKeyId[_id]));
});
parentResult[childCollectionNode.linkName] = filterAssembledData(
data,
{ limit, skip }
);
});
}
if (strategy === 'one-meta') {
parent.results.forEach(parentResult => {
if (!parentResult[fieldStorage]) {
return;
}
const _id = parentResult[fieldStorage]._id;
parentResult[childCollectionNode.linkName] = filterAssembledData(
resultsByKeyId[_id],
{ limit, skip }
);
});
}
if (strategy === 'many-meta') {
parent.results.forEach(parentResult => {
const _ids = _.pluck(parentResult[fieldStorage], '_id');
let data = [];
_ids.forEach(_id => {
data.push(_.first(resultsByKeyId[_id]));
});
parentResult[childCollectionNode.linkName] = filterAssembledData(
data,
{ limit, skip }
);
});
}
};
function filterAssembledData(data, { limit, skip }) {
if (limit && Array.isArray(data)) {
return data.slice(skip, limit);
}
return data;
}
function assembleData(childCollectionNode, result, {fieldStorage, strategy}) {
const filters = createSearchFilters(result, fieldStorage, strategy, false);
return sift(filters, childCollectionNode.results);
}

0
lib/query/testing/link-cache/fixtures.js Normal file → Executable file
View file

3
lib/query/testing/link-cache/server.test.js Normal file → Executable file
View file

@ -9,8 +9,9 @@ import {
} from './collections';
describe('Query Link Denormalization', function() {
before(() => {
before((done) => {
createFixtures();
done();
});
it('Should not cache work with nested options', function() {

View file

@ -1,10 +1,16 @@
import { createQuery } from 'meteor/cultofcoders:grapher';
import dot from 'dot-object';
import Comments from './bootstrap/comments/collection.js';
import Posts from './bootstrap/posts/collection.js';
import Tags from './bootstrap/tags/collection.js';
import './metaFilters.server.test';
import './reducers.server.test';
import './link-cache/server.test';
// Used in some tests below
const Users = new Mongo.Collection('__many_inversed_users');
const Restaurants = new Mongo.Collection('__many_inversed_restaurants');
describe('Hypernova', function() {
it('Should fetch One links correctly', function() {
const data = createQuery({
@ -105,6 +111,71 @@ describe('Hypernova', function() {
});
});
it('Should fetch Many - inversed links correctly #2', function() {
const post1Id = Posts.insert({ name: 'Post1' });
const post2Id = Posts.insert({ name: 'Post2' });
const post3Id = Posts.insert({ name: 'Post3' });
const post4Id = Posts.insert({ name: 'Post4' });
const tag1Id = Tags.insert({ name: 'Tag1' });
const tag2Id = Tags.insert({ name: 'Tag2' });
const tag3Id = Tags.insert({ name: 'Tag3' });
function addTags(postId, tagIds) {
Posts.update(postId, {
$set: {
tagIds,
},
});
}
addTags(post1Id, [tag1Id, tag2Id]);
addTags(post2Id, [tag1Id]);
addTags(post3Id, [tag2Id, tag3Id]);
addTags(post4Id, [tag3Id, tag1Id]);
const data = createQuery({
tags: {
$filters: {
_id: { $in: [tag1Id, tag2Id, tag3Id] },
},
name: 1,
posts: {
name: 1,
},
},
}).fetch();
console.log(JSON.stringify(data, null, 2));
const tag1Data = _.find(data, doc => doc.name === 'Tag1');
const tag2Data = _.find(data, doc => doc.name === 'Tag2');
const tag3Data = _.find(data, doc => doc.name === 'Tag3');
function hasPost(tag, postName) {
return !!_.find(tag.posts, post => post.name === postName);
}
assert.lengthOf(tag1Data.posts, 3);
assert.isTrue(hasPost(tag1Data, 'Post1'));
assert.isTrue(hasPost(tag1Data, 'Post2'));
assert.isTrue(hasPost(tag1Data, 'Post4'));
assert.lengthOf(tag2Data.posts, 2);
assert.isTrue(hasPost(tag2Data, 'Post1'));
assert.isTrue(hasPost(tag2Data, 'Post3'));
assert.lengthOf(tag3Data.posts, 2);
assert.isTrue(hasPost(tag3Data, 'Post3'));
assert.isTrue(hasPost(tag3Data, 'Post4'));
Posts.remove({
_id: { $in: [post1Id, post2Id, post3Id, post4Id] },
});
Tags.remove({
_id: { $in: [tag1Id, tag2Id, tag3Id] },
});
});
it('Should fetch One-Meta links correctly', function() {
const data = createQuery({
posts: {
@ -171,16 +242,16 @@ describe('Hypernova', function() {
});
});
it('Should fetch Many-Meta links correctly where parent is One link', function () {
it('Should fetch Many-Meta links correctly where parent is One link', function() {
const data = createQuery({
posts: {
$options: { limit: 5 },
author: {
groups: {
isAdmin: 1
}
}
}
isAdmin: 1,
},
},
},
}).fetch();
_.each(data, post => {
@ -525,9 +596,6 @@ describe('Hypernova', function() {
assert.isTrue(group.authors.length > 0);
});
const Users = new Mongo.Collection('__many_inversed_users');
const Restaurants = new Mongo.Collection('__many_inversed_restaurants');
it('Should fetch Many - inversed links correctly when the field is not the first', function() {
Restaurants.addLinks({
users: {
@ -577,11 +645,10 @@ describe('Hypernova', function() {
metadata: {
language: {
abbr: 1,
}
}
}
}
},
},
},
},
});
const data = query.fetch();

View file

@ -1,6 +1,6 @@
Package.describe({
name: 'cultofcoders:grapher',
version: '1.3.6',
version: '1.3.7_4',
// Brief, one-line summary of the package.
summary: 'Grapher is a data fetching layer on top of Meteor',
// URL to the Git repository containing the source code for this package.