grapher/docs/query.md
2016-09-19 14:27:38 +03:00

7.9 KiB

Query

Creating a query for a collection client-side, must be exposed server side. Otherwise it will not work.

Queries are a way to specify which data you want from the server using links as the backbone for creating the data graph. Queries can be reactive (using Meteor's pub/sub system) or static (using method call) or direct if the call is done server side.

Let's configure some links, and then see how we can query them into the database.

Assuming we have these collections: Authors, Comments, Posts, Groups, Category:

  • Author has many posts.
  • Author can belong in many groups.
  • Posts has many comments.
  • Posts has one category.
  • Comments has a single author.

Notes to keep in mind:

  • By default type is one, but you should specify it for clarity.
  • Field is not necessary because it will auto-generate a unique field based on collection name, linked collection name, and link name. We recommend specifying it for having more verbose code.
Don't panic!

We'll start defining our links, if something stops making sense. Review the Collection Links documentation again.

Authors.addLinks({
    groups: {
        collection: Groups,
        field: 'groupIds',
        type: 'many'
    },
    posts: {
        collection: Posts,
        inversedBy: 'author'
    },
    // Resolver links only work on server-side or with non-reactive queries
    likesOnFacebook: {
        // in this case resolver will receive: object, filters, options, userId
        // since resolver is run server side, the author will be the full object with all the fields.
        resolver(author) {
            // do a sync call to retrieve the likes on facebook using author object.
            return count;
        }
    }
});

Posts.addLinks({
    author: {
        collection: Authors,
        type: 'one',
        field: 'authorId'
    },
    // it can have a lot of comments, so it's safer if we store the link in Comments collection
    comments: { 
        collection: Comments,
        inversedBy: 'post'
    },
    category: {
        collection: Categories,
        type: 'many',
        field: 'categoryIds'
    }
});

Comments.addLinks({
    author: {
        collection: Authors,
        field: 'authorId'
    },
    post: {
        collection: Posts,
        field: 'postId'
    }
});

Category.addLinks({
    author: {
        collection: Authors,
        inversedBy: 'category',
    }
});

Groups.addLinks({
    authors: {
        collection: Authors,
        inversedBy: 'groups'
    }
});

Perfect. Now that we defined our relationships we can query our database. Assuming we exposed "Posts" server-side, we can fetch the query client-side.

Notes:

  • Use {} to specify a link, and 1 for a field.
  • "_id" will always be fetched
  • You must always specify the fields you need, otherwise it will only fetch _id
  • If you want all fields, pass in {$all: 1}
const query = Posts.createQuery({
    title: 1,
    // if there are no custom fields specified, it will retrieve all fields.
    author: {
        // if you have a nested object, (no link named profile) it will not try to fetch the link, but rather give you only the fields you need.
        profile: {
            firstname: 1
            lastname: 1
        },
        likesOnFacebook: 1
    } 
    comments: {
        text: 1,
        // if you don't specify any local fields for the author, only "_id" field will be fetched
        // use $all: 1, to get all fields
        // this will enforce the use of query and retrieve only the data you need.
        author: {
            groups: {
                name: 1
            }
        }
    }
});

Now that we have created our query, we have two options of fetching the data.

  1. Reactively (via subscribe)

const subsHandle = query.subscribe();
const data = query.fetch();

query.unsubscribe();
query.fetch(); // now it will fail because you did not provide a callback, because when you unsubscribe, we delete the subscriptionHandle

Important! If you previously subscribed, fetching will be done client side using client-side collections, if you did not previously subscribe, you need to provide a callback because data will be fetched via a method call.

If you don't want to use .fetch() you can also use the collections as you previously used to:

Posts.find().fetch()
Comments.find({postId: 'XXXXXX'}).fetch()
  1. Statically (via method call)

query.fetch((error, response) => {
    // if no error occured, the response will look something like this
    [
        {
            _id: 'XXXXXXXXXXXXXXX',
            title: 'Hello World!',
            author: {
                profile: {
                    firstname: 'John',
                    lastname: 'Smith'
                }
                likesOnFacebook: 200
            },
            comments: [
                {
                    text: 'Nice Post',
                    author: {
                        _id: 'XXXXXXXXXXXXX'
                    },
                    groups: [
                        {
                            _id: 'XXXXXXXXXXX',
                            name: 'Group 1'
                        }
                    ]
                }
            ]
        },
        ...
    ]
});

Filtering queries

const query = Posts.createQuery({
    $filters: {isApproved: true} // this will find only posts that have isApproved: true
    $options: {
        limit: 100
    }
    title: 1
    comments: {
        $filters: { // this will only search the comments that have isNotSpam: true
            isNotSpam: true
        }
    }
});

Dynamic Filtering

You can pass params to your query, they will be available in every $filter() function. Using $filter() gives you enough control to filters and options. So $filters and $options may be omitted.

const query = Posts.createQuery({
    $filter({filters, options, params}) {
        filters.isApproved = params.isApproved
    }
    title: 1
    comments: {
        $filter({filters, options, params}) {
            if (params.allowSpamComments) {
                filters.isNotSpam = undefined; // $filter() overrides $filters and $options
            }
        }
        $filters: { // this will only search the comments that have isNotSpam: true
            isNotSpam: true
        }
    }
}, {
    isApproved: true,
    allowSpamComments: true
});

Control parameters however you wish:

query.setParams({
    allowSpamComments: false
});

If you are using this query reactively, the query will re-subscribe.

Using it with React And react-meteor-data package:

import query from './listPostsQuery.js';

export default createContainer(() => {
    const handle = query.subscribe();
    const posts = query.fetch();
    
    return {
        isReady: handle.isReady(),
        posts: posts
    }
}, PostList);

Security and Performance

By default the options "disableOplog", "pollingIntervalMs", "pollingThrottleMs" are not available on the client. You can control them in the firewall of your exposure.

Creating a query without the collection object

import { createQuery } from 'meteor/cultofcoders:grapher';

createQuery({
    posts: {
        comments: {
            text: 1
        }
    }
});

posts is the name of the collection. (when you create new Mongo.Collection("xxx"), "xxx" is the name of your collection)

Getting all the fields

Though this is not recommended, sometimes especially when you are just testing around, you want to see all the fields

createQuery({
    posts: {
        $all: 1,
        comments: {
            $all: 1
        }
    }
});

The query above will fetch all the fields from every posts and every comment of every post.

React Integration

For integration with React try out cultofcoders:grapher-react package