From 1081bc175905978e0c76fd5ae89fa10163c8684d Mon Sep 17 00:00:00 2001 From: Rockafella Date: Wed, 9 Jul 2014 10:05:55 +0400 Subject: [PATCH 1/4] Fix conflicting tests --- test/fortune/hooks.js | 32 ++++++++++++++++++++++++-------- test/mongoose_middleware.js | 2 +- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/test/fortune/hooks.js b/test/fortune/hooks.js index bcbf89dae..444ae3535 100644 --- a/test/fortune/hooks.js +++ b/test/fortune/hooks.js @@ -1,5 +1,6 @@ var request = require('supertest'); var should = require('should'); +var RSVP = require('rsvp'); module.exports = function(options){ var ids, app, baseUrl; @@ -54,14 +55,29 @@ module.exports = function(options){ }); describe("native mongoose middleware", function(){ it("should be able to expose mongoose api to resources", function(done){ - request(baseUrl).get("/houses/" + ids.houses[0]) - .expect(200) - .end(function(err, res){ - should.not.exist(err); - var body = JSON.parse(res.text); - (body.houses[0].address).should.match(/mongoosed$/); - done(); - }); + new RSVP.Promise(function(resolve){ + request(baseUrl).post("/houses") + .set("content-type", "application/json") + .send(JSON.stringify({ + houses: [{ + address: "mongoose-" + }] + })) + .end(function(err, res){ + should.not.exist(err); + var body = JSON.parse(res.text); + resolve(body.houses[0].id); + }); + }).then(function(createdId){ + request(baseUrl).get("/houses/" + createdId) + .expect(200) + .end(function(err, res){ + should.not.exist(err); + var body = JSON.parse(res.text); + (body.houses[0].address).should.match(/mongoosed$/); + done(); + }); + }); }); }); }; \ No newline at end of file diff --git a/test/mongoose_middleware.js b/test/mongoose_middleware.js index 158223fb0..bfde2cf84 100644 --- a/test/mongoose_middleware.js +++ b/test/mongoose_middleware.js @@ -1,7 +1,7 @@ module.exports = function(schema, options){ var getter = function(v){ - return v += 'mongoosed'; + return /^mongoose-$/.test(v) ? v += 'mongoosed' : v; }; var paths = options.paths; schema.pre('init', function(next, doc){ From 5c971372cd75d75fe9fcc68023593c5e4c8604a9 Mon Sep 17 00:00:00 2001 From: Rockafella Date: Wed, 9 Jul 2014 12:16:18 +0400 Subject: [PATCH 2/4] 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(); + }) + }); }); }); From 6902ebcf709ff528b6e6350b3374a35f515e7444 Mon Sep 17 00:00:00 2001 From: Rockafella Date: Wed, 9 Jul 2014 12:18:26 +0400 Subject: [PATCH 3/4] Improve formatting --- lib/adapters/mongodb.js | 81 ++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/lib/adapters/mongodb.js b/lib/adapters/mongodb.js index 0993e9ce6..ef72a2fdb 100644 --- a/lib/adapters/mongodb.js +++ b/lib/adapters/mongodb.js @@ -273,56 +273,55 @@ 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)){ - //Strict date equality - m = moment(val); - - if(m.format("YYYY-MM-DD") === val){ + _.each(query, function(val, key){ + var m; + if(model.schema.tree[key] === Date && _.isString(val)){ + //Strict date equality + m = moment(val); + + if(m.format("YYYY-MM-DD") === val){ + query[key] = { + $gte: val, + $lte: moment(val).add("days", 1).format("YYYY-MM-DD") + }; + } + }else if ((model.schema.tree[key] === Date || model.schema.tree[key] === Number) && _.isObject(val)){ + //gt/gte/lt/lte for dates and numbers + query[key] = _.reduce(val, function(memo, opVal, op){ + memo[{ "gt": "$gt", "gte": "$gte", "lt": "$lt", "lte": "$lte" }[op] || op] = opVal; + return memo; + }, {}); + }else if (_.isString(val.in || val.$in)){ query[key] = { - $gte: val, - $lte: moment(val).add("days", 1).format("YYYY-MM-DD") + $in: (val.in || val.$in).split(',') }; + }else if (_.isObject(val) && _.isString(val.regex)){ + //regex + query[key] = { + $regex: val.regex ? val.regex : '', + $options: val.options ? val.options : '' + }; + }else if(key === 'or' || key === 'and'){ + query['$' + key] = _.map(val, parseQuery); + delete query[key]; } - }else if ((model.schema.tree[key] === Date || model.schema.tree[key] === Number) && _.isObject(val)){ - //gt/gte/lt/lte for dates and numbers - query[key] = _.reduce(val, function(memo, opVal, op){ - memo[{ "gt": "$gt", "gte": "$gte", "lt": "$lt", "lte": "$lte" }[op] || op] = opVal; - return memo; - }, {}); - }else if (_.isString(val.in || val.$in)){ - query[key] = { - $in: (val.in || val.$in).split(',') - }; - }else if (_.isObject(val) && _.isString(val.regex)){ - //regex - query[key] = { - $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}; - }else{ - dbQuery = _.clone(query); + if(_.isObject(query)){ + if(_.isArray(query)) { + if(query.length) dbQuery[pk] = {$in: query}; + }else{ + dbQuery = _.clone(query); - deepReplaceFalsies(dbQuery); + deepReplaceFalsies(dbQuery); - if(query.id){ - dbQuery[pk] = query.id; - delete dbQuery.id; + if(query.id){ + dbQuery[pk] = query.id; + delete dbQuery.id; + } } } - } return dbQuery; } From 6dfe11f2cca48177bde0f55f91e38ace7e4c071e Mon Sep 17 00:00:00 2001 From: Rockafella Date: Wed, 9 Jul 2014 12:24:50 +0400 Subject: [PATCH 4/4] Remove debug code --- lib/querytree.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/querytree.js b/lib/querytree.js index 17303ba18..33513bc0b 100644 --- a/lib/querytree.js +++ b/lib/querytree.js @@ -59,9 +59,7 @@ function parseQuery(query, requestedResource){ }else{ 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; - }); + return exports.parse(requestedResource, subq); })); }else{ //Plain field