Skip to content

Commit

Permalink
Merge pull request #4 from limit-zero/merge-options
Browse files Browse the repository at this point in the history
Do not deep merge "special" objects by default
  • Loading branch information
zarathustra323 authored Jul 6, 2018
2 parents 2f4f0fc + 3f5725f commit 00abc6f
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 10 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ Use the class constructor to configure the settings for the paginated query.
#### constructor(Model, { criteria = {}, pagination = {}, sort = {}, projection }, options = {})
`Model`: The Mongoose model instance to query. _Required._

`criteria`: A query criteria object to apply to the paginated query. Can be any MongoDB query. For example: `{ deleted: false }` or `{ age: { $gt: 30 } }`. Optional.
`criteria`: A query criteria object to apply to the paginated query. Can be any MongoDB query. For example: `{ deleted: false }` or `{ age: { $gt: 30 } }`. The criteria will be deeply merged, but will ignore "special" objects such as `ObjectId` and, by default, will only merge "plain objects." Optional.

`pagination`: The pagination parameters object. Can accept a `first` and/or `after` property. The `first` value specifies the limit/page size. The `after` value specifies the cursor to start at when paginating. For example: `{ first: 50, after: 'some-cursor-value' }` would return the first 50 edges after the provided cursor. By default the results will be limited to 10 edges. Optional.

`sort`: Specifies the sort options. The `field` property specifies the field to sort by, and the order defines the direction. For example: `{ field: 'name', order: -1 }` would sort the edges by name, descending. By default the edges are sorted by ID, ascending. Optional.

`projection`: Specifies the fields to return from the database. For example: `{ field: 1 }` or `{ field: 0 }` would include or exclude the specified field, respectively. If left `undefined`, or as an empty object, all fields will be returned (which is the default behavior). Optional.

`options`: Specifies additional configuration options, such as default limit, max limit, sort collation, and sort created field.
`options`: Specifies additional configuration options, such as default limit, max limit, sort collation, object merge options, and sort created field.

Complete example:
```js
Expand Down Expand Up @@ -69,9 +69,9 @@ Use the class constructor to configure the settings for the type-ahead query.

`term`: The search term. Can/should be a partial phrase. _Required._

`criteria`: Additional MongoDB query criteria to apply when querying. For example `{ deleted: false }`. Optional.
`criteria`: Additional MongoDB query criteria to apply when querying. For example `{ deleted: false }`. The criteria will be deeply merged, but will ignore "special" objects such as `ObjectId` and, by default, will only merge "plain objects." Optional.

`options`: The type-ahead configuration options object. Has three possible properties:
`options`: The type-ahead configuration options object. Has three type-ahead related properties:
```js
{
// Determines how the regex is constructed.
Expand All @@ -86,6 +86,7 @@ Use the class constructor to configure the settings for the type-ahead query.
caseSensitive: false,
}
```
In addition, the `options` will also accept the optional `mergeOptions` object property htat can be used to override the default merge rules.

#### paginate(Model, pagination = {}, options = {})
Creates a `Pagination` instance using the constructed `TypeAhead` options.
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
},
"dependencies": {
"deepmerge": "^2.1.0",
"escape-string-regexp": "^1.0.5"
"escape-string-regexp": "^1.0.5",
"is-plain-object": "^2.0.4"
},
"optionalDependencies": {
"@limit0/graphql-custom-types": "^1.0.1",
Expand Down
14 changes: 11 additions & 3 deletions src/pagination.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
const deepMerge = require('deepmerge');
const isPlainObject = require('is-plain-object');
const Sort = require('./sort');
const Limit = require('./limit');

const mergeOptions = { isMergeableObject: isPlainObject };

class Pagination {
/**
* Constructor.
Expand All @@ -18,7 +21,8 @@ class Pagination {
* @param {string} params.sort.field The sort field name.
* @param {string} params.sort.order The sort order. Either 1/-1 or asc/desc.
* @param {?object} params.projection The field projection (fields to return).
* @param {object} options Additional sort and limit options. See the corresponding classes.
* @param {object} options Additional sort, limit and criteria merge options.
* See the corresponding classes.
*/
constructor(Model, {
criteria = {},
Expand All @@ -31,8 +35,12 @@ class Pagination {
// Set the Model to use for querying.
this.Model = Model;

// Sets the options for deep merging criteria object.
// If not set, will only merge plain objects, per `is-plain-object`.
this.mergeOptions = options.mergeOptions || mergeOptions;

// Set/merge any query criteria.
this.criteria = deepMerge({}, criteria);
this.criteria = deepMerge({}, criteria, this.mergeOptions);

// Set the limit and after cursor.
const { first, after } = pagination;
Expand Down Expand Up @@ -155,7 +163,7 @@ class Pagination {
const run = async () => {
const { field, order } = this.sort;

const filter = deepMerge({}, this.criteria);
const filter = deepMerge({}, this.criteria, this.mergeOptions);
const limits = {};
const ors = [];

Expand Down
9 changes: 7 additions & 2 deletions src/type-ahead.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
const escapeRegex = require('escape-string-regexp');
const deepMerge = require('deepmerge');
const isPlainObject = require('is-plain-object');
const Pagination = require('./pagination');

const mergeOptions = { isMergeableObject: isPlainObject };

class TypeAhead {
constructor(field, term, criteria = {}, options = {}) {
this.mergeOptions = options.mergeOptions || mergeOptions;

this.options = options;
this.values = {};

Expand Down Expand Up @@ -40,11 +45,11 @@ class TypeAhead {
}

set criteria(criteria) {
this.values.criteria = deepMerge({}, criteria);
this.values.criteria = deepMerge({}, criteria, this.mergeOptions);
}

get criteria() {
return deepMerge({}, this.values.criteria);
return deepMerge({}, this.values.criteria, this.mergeOptions);
}

paginate(Model, pagination = {}, options = {}) {
Expand Down
28 changes: 28 additions & 0 deletions test/pagination.spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
require('./connection');
const Model = require('./mongoose/model');
const { Types } = require('mongoose');
const Pagination = require('../src/pagination');
const sandbox = sinon.createSandbox();

const { ObjectId } = Types;

const data = [
{ name: 'foo', deleted: false },
{ name: 'Foo', deleted: false },
Expand Down Expand Up @@ -46,6 +49,31 @@ describe('pagination', function() {
done();
});

it('should not merge special objects by default.', async function() {
const id = ObjectId('5b3980c956d5a405cc4007c5');
const criteria = {
foo: 'bar',
id,
bar: { a: 'b' },
};
const instance = new Pagination(Model, { criteria });
expect(instance.criteria).to.deep.equal(criteria);
expect(instance.criteria.id).to.be.an.instanceOf(ObjectId);
});

it('should merge special objects when specifically instructured to.', async function() {
const mergeOptions = {};
const id = ObjectId('5b3980c956d5a405cc4007c5');
const criteria = {
foo: 'bar',
id,
bar: { a: 'b' },
};
const instance = new Pagination(Model, { criteria }, { mergeOptions });
expect(instance.criteria.id).to.be.an('object');
expect(instance.criteria.id).to.not.be.an.instanceOf(ObjectId);
});

describe('#getTotalCount', function() {
[1, 10, 50].forEach((first) => {
const pagination = { first };
Expand Down
28 changes: 28 additions & 0 deletions test/type-ahead.spec.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
require('./connection');
const TypeAhead = require('../src/type-ahead');
const Model = require('./mongoose/model');
const { Types } = require('mongoose');
const Pagination = require('../src/pagination');

const { ObjectId } = Types;

describe('type-ahead', function() {
it('should not merge special objects by default.', async function() {
const id = ObjectId('5b3980c956d5a405cc4007c5');
const criteria = {
foo: 'bar',
id,
bar: { a: 'b' },
};
const instance = new TypeAhead('foo', 'bar', criteria);
expect(instance.criteria).to.deep.equal(criteria);
expect(instance.criteria.id).to.be.an.instanceOf(ObjectId);
});

it('should merge special objects when specifically instructured to.', async function() {
const mergeOptions = {};
const id = ObjectId('5b3980c956d5a405cc4007c5');
const criteria = {
foo: 'bar',
id,
bar: { a: 'b' },
};
const instance = new TypeAhead('foo', 'bar', criteria, { mergeOptions });
expect(instance.criteria.id).to.be.an('object');
expect(instance.criteria.id).to.not.be.an.instanceOf(ObjectId);
});

describe('.field', function() {
it('should throw an error when empty', function(done) {
const message = 'A type ahead field must be specified.';
Expand Down

0 comments on commit 00abc6f

Please sign in to comment.