From 5c971372cd75d75fe9fcc68023593c5e4c8604a9 Mon Sep 17 00:00:00 2001 From: Rockafella Date: Wed, 9 Jul 2014 12:16:18 +0400 Subject: [PATCH] Complex OR and AND conditions in filters --- lib/adapters/mongodb.js | 13 +++++++ lib/fortune.js | 2 +- lib/querytree.js | 14 ++++++-- test/fortune/fields_and_filters.js | 58 ++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 4 deletions(-) diff --git a/lib/adapters/mongodb.js b/lib/adapters/mongodb.js index 693bc2043..0993e9ce6 100644 --- a/lib/adapters/mongodb.js +++ b/lib/adapters/mongodb.js @@ -272,6 +272,8 @@ adapter.findMany = function(model, query, projection) { var pk = model.pk || "_id"; + function parseQuery(query){ + _.each(query, function(val, key){ var m; if(model.schema.tree[key] === Date && _.isString(val)){ @@ -300,9 +302,13 @@ adapter.findMany = function(model, query, projection) { $regex: val.regex ? val.regex : '', $options: val.options ? val.options : '' }; + }else if(key === 'or' || key === 'and'){ + query['$' + key] = _.map(val, parseQuery); + delete query[key]; } }); + if(_.isObject(query)){ if(_.isArray(query)) { if(query.length) dbQuery[pk] = {$in: query}; @@ -316,6 +322,13 @@ adapter.findMany = function(model, query, projection) { delete dbQuery.id; } } + } + + return dbQuery; + } + + if (_.isObject(query)){ + query = parseQuery(query); }else if(typeof query === 'number' && arguments.length === 2){ //Just for possible backward compatibility issues projection = projection || {}; diff --git a/lib/fortune.js b/lib/fortune.js index 1fb231ad7..34b15791a 100644 --- a/lib/fortune.js +++ b/lib/fortune.js @@ -234,7 +234,7 @@ Fortune.prototype.resource = function(name, schema, options, schemaCallback) { * @return {Object} */ Fortune.prototype._preprocessSchema = function (schema) { - ['id', 'href', 'links', 'in'].forEach(function (reservedKey) { + ['id', 'href', 'links', 'in', 'or', 'and'].forEach(function (reservedKey) { if (schema.hasOwnProperty(reservedKey)) { delete schema[reservedKey]; console.warn('Reserved key "' + reservedKey + '" is not allowed.'); diff --git a/lib/querytree.js b/lib/querytree.js index 40847ed19..17303ba18 100644 --- a/lib/querytree.js +++ b/lib/querytree.js @@ -57,9 +57,17 @@ function parseQuery(query, requestedResource){ //String ref freeze[key] = createSubrequest(q, schemaBranch); }else{ - //Plain field - //Do nothing. fetchIds should have this untouched - freeze[key] = q; + if (key === 'or' || key === 'and' || key === '$and' || key === '$or'){ + freeze[key] = RSVP.all(_.map(q, function(subq){ + return exports.parse(requestedResource, subq).then(function(result){ + return result; + }); + })); + }else{ + //Plain field + //Do nothing. fetchIds should have this untouched + freeze[key] = q; + } } }); return RSVP.hash(freeze); diff --git a/test/fortune/fields_and_filters.js b/test/fortune/fields_and_filters.js index 668426b84..6ba776af7 100644 --- a/test/fortune/fields_and_filters.js +++ b/test/fortune/fields_and_filters.js @@ -324,6 +324,28 @@ module.exports = function(options){ done(); }); }); + it('should support or query', function(done){ + request(baseUrl).get('/people?filter[or][0][name]=Dilbert&filter[or][1][email]=robert@mailbert.com&sort=name') + .expect(200) + .end(function(err, res){ + should.not.exist(err); + var body = JSON.parse(res.text); + (body.people.length).should.equal(2); + (body.people[0].name).should.equal('Dilbert'); + (body.people[1].name).should.equal('Robert'); + done(); + }); + }); + it('should have id filter', function(done){ + request(baseUrl).get('/cars?filter[id]=' + ids.cars[0]) + .expect(200) + .end(function(err, res){ + should.not.exist(err); + var body = JSON.parse(res.text); + (body.cars[0].id).should.equal(ids.cars[0]); + done(); + }); + }); describe('filtering by related objects fields', function(){ beforeEach(function(done){ neighbourhood(adapter, ids).then(function(){ @@ -362,6 +384,42 @@ module.exports = function(options){ }); }); }); + it('should be able to apply OR filter to related resources', function(done){ + request(baseUrl).get('/cars?filter[or][0][owner][soulmate]=' + ids.people[0] + '&filter[or][1][id]=' + ids.cars[0]) + .expect(200) + .end(function(err, res){ + should.not.exist(err); + var body = JSON.parse(res.text); + (body.cars.length).should.equal(2); + var one = _.findWhere(body.cars, {id: ids.cars[0]}); + var two = _.findWhere(body.cars, {id: ids.cars[1]}); + should.exist(one); + should.exist(two); + done(); + }); + }); + it('should be able to apply AND filter to related resources', function(done){ + request(baseUrl).get('/pets?filter[and][0][owner][soulmate][email]=' + ids.people[0] + '&filter[and][1][owner][email]=' + ids.people[1]) + .expect(200) + .end(function(err, res){ + should.not.exist(err); + var body = JSON.parse(res.text); + (body.pets.length).should.equal(1); + (body.pets[0].id).should.equal(ids.pets[0]); + done(); + }) + }); + it('should be able to nest OR and AND filters', function(done){ + request(baseUrl).get('/houses?filter[or][0][and][0][owners][in]=' + ids.people[0]) + .expect(200) + .end(function(err, res){ + should.not.exist(err); + var body = JSON.parse(res.text); + (body.houses.length).should.equal(1); + (body.houses[0].id).should.equal(ids.houses[0]); + done(); + }) + }); }); });