diff --git a/lib/waterline/utils/query/forge-stage-two-query.js b/lib/waterline/utils/query/forge-stage-two-query.js index 6fd6ea1f6..e48ecfde6 100644 --- a/lib/waterline/utils/query/forge-stage-two-query.js +++ b/lib/waterline/utils/query/forge-stage-two-query.js @@ -873,28 +873,47 @@ module.exports = function forgeStageTwoQuery(query, orm) { if (_.isEqual(query.populates[populateAttrName], {})) { query.populates[populateAttrName] = true; } - // Otherwise, this simply must be `true`. Otherwise it's invalid. + // Otherwise, this must either be `true` or a valid subcriteria containing only `omit` or `select`. else { - - if (query.populates[populateAttrName] !== true) { + // Only allow a plain dictionary with just `select` and/or `omit`. + // Anything else isn't valid for a singular ("model") association. + if (query.populates[populateAttrName] !== true && !( + _.isPlainObject(query.populates[populateAttrName]) && + _.difference(Object.keys(query.populates[populateAttrName]), ['select', 'omit']).length === 0 + )) { throw buildUsageError( - 'E_INVALID_POPULATES', - 'Could not populate `'+populateAttrName+'`. '+ - 'This is a singular ("model") association, which means it never refers to '+ - 'more than _one_ associated record. So passing in subcriteria (i.e. as '+ - 'the second argument to `.populate()`) is not supported for this association, '+ - 'since it generally wouldn\'t make any sense. But that\'s the trouble-- it '+ - 'looks like some sort of a subcriteria (or something) _was_ provided!\n'+ - '(Note that subcriterias consisting ONLY of `omit` or `select` are a special '+ - 'case that _does_ make sense. This usage will be supported in a future version '+ - 'of Waterline.)\n'+ - '\n'+ - 'Here\'s what was passed in:\n'+ - util.inspect(query.populates[populateAttrName], {depth: 5}), - query.using + 'E_INVALID_POPULATES', + 'Could not populate `'+populateAttrName+'`. '+ + 'This is a singular ("model") association, which means it never refers to '+ + 'more than _one_ associated record. So passing in subcriteria (i.e. as '+ + 'the second argument to `.populate()`) is not supported for this association, '+ + 'since it generally wouldn\'t make any sense. But that\'s the trouble-- it '+ + 'looks like some sort of a subcriteria (or something) _was_ provided!\n'+ + '(Note that subcriterias consisting ONLY of `omit` or `select` are a special '+ + 'case that _does_ make sense.\n'+ + '\n'+ + 'Here\'s what was passed in:\n'+ + util.inspect(query.populates[populateAttrName], {depth: 5}), + query.using ); }//-• + // Ensure that only `select` or `omit` is provided, but not both. + if (_.isPlainObject(query.populates[populateAttrName]) && + query.populates[populateAttrName].select && + query.populates[populateAttrName].omit) { + throw buildUsageError( + 'E_INVALID_POPULATES', + 'Could not populate `'+populateAttrName+'`. '+ + 'This is a singular ("model") association, and while subcriterias consisting '+ + 'ONLY of `omit` or `select` are supported, providing both `select` AND `omit` '+ + 'at the same time is not allowed.\n'+ + '\n'+ + 'Here\'s what was passed in:\n'+ + util.inspect(query.populates[populateAttrName], {depth: 5}), + query.using + ); + }//-• }//>-• } diff --git a/test/unit/query/associations/populateArray.js b/test/unit/query/associations/populateArray.js index 28a079993..b8c77239a 100644 --- a/test/unit/query/associations/populateArray.js +++ b/test/unit/query/associations/populateArray.js @@ -123,5 +123,65 @@ describe('Collection Query ::', function() { return done(); }); }); + + it('should allow populate with select subcriteria for singular associations', function(done) { + Car.find() + .populate('driver', { + select: ['name'] + }) + .exec(function(err, car) { + if (err) { + return done(err); + } + + assert(car[0].driver); + assert(car[0].driver.name); + return done(); + }); + }); + + it('should allow populate with omit subcriteria for singular associations', function(done) { + Car.find() + .populate('driver', { + omit: ['car'] + }) + .exec(function(err, car) { + if (err) { + return done(err); + } + + assert(car[0].driver); + assert(car[0].driver.name); + return done(); + }); + }); + + it('should allow populate with empty object (converts to true)', function(done) { + Car.find() + .populate('driver', {}) + .exec(function(err, car) { + if (err) { + return done(err); + } + + assert(car[0].driver); + assert(car[0].driver.name); + return done(); + }); + }); + + it('should throw error when both select and omit are provided together', function(done) { + Car.find() + .populate('driver', { + select: ['name'], + omit: ['id'] + }) + .exec(function(err, car) { + assert(err); + assert.equal(err.code, 'E_INVALID_POPULATES'); + assert(err.message.indexOf('providing both `select` AND `omit`') > -1); + return done(); + }); + }); }); });