Refactor logic of options.allowExtendedOperators

Implement logic to allowExtendedOperators options
per request, per Model and Datasource.
This commit is contained in:
Matteo Padovano 2016-06-16 16:34:38 +02:00
parent 023856cbe9
commit 733ad1a024
2 changed files with 349 additions and 43 deletions

View File

@ -181,6 +181,31 @@ DataAccessObject.getConnector = function() {
return this.getDataSource().connector;
};
/**
* Verify if allowExtendedOperators is enabled
* @options {Object} [options] Optional options to use.
* @property {Boolean} allowExtendedOperators.
* @returns {Boolean} Returns `true` if allowExtendedOperators is enabled, else `false`.
*/
DataAccessObject._allowExtendedOperators = function(options) {
options = options || {};
var Model = this;
var dsSettings = this.getDataSource().settings;
var allowExtendedOperators = dsSettings.allowExtendedOperators;
// options settings enable allowExtendedOperators per request (for example if
// enable allowExtendedOperators only server side);
// model settings enable allowExtendedOperators only for specific model.
// dataSource settings enable allowExtendedOperators globally (all models);
// options -> model -> dataSource (connector)
if (options.hasOwnProperty('allowExtendedOperators')) {
allowExtendedOperators = options.allowExtendedOperators === true;
} else if (Model.settings && Model.settings.hasOwnProperty('allowExtendedOperators')) {
allowExtendedOperators = Model.settings.allowExtendedOperators === true;
}
return allowExtendedOperators;
};
// Empty callback function
function noCallback(err, result) {
// NOOP
@ -720,9 +745,9 @@ DataAccessObject.upsertWithWhere = function(where, data, options, cb) {
function callConnector() {
try {
ctx.where = removeUndefined(ctx.where);
ctx.where = Model._coerce(ctx.where);
ctx.where = Model._coerce(ctx.where, options);
update = removeUndefined(update);
update = Model._coerce(update);
update = Model._coerce(update, options);
} catch (err) {
return process.nextTick(function() {
cb(err);
@ -1110,7 +1135,7 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb)
query.limit = 1;
try {
this._normalize(query);
this._normalize(query, options);
} catch (err) {
process.nextTick(function() {
cb(err);
@ -1413,10 +1438,12 @@ var operators = {
/*
* Normalize the filter object and throw errors if invalid values are detected
* @param {Object} filter The query filter object
* @options {Object} [options] Optional options to use.
* @property {Boolean} allowExtendedOperators.
* @returns {Object} The normalized filter object
* @private
*/
DataAccessObject._normalize = function(filter) {
DataAccessObject._normalize = function(filter, options) {
if (!filter) {
return undefined;
}
@ -1497,7 +1524,7 @@ DataAccessObject._normalize = function(filter) {
var handleUndefined = this._getSetting('normalizeUndefinedInQuery');
// alter configuration of how removeUndefined handles undefined values
filter = removeUndefined(filter, handleUndefined);
this._coerce(filter.where);
this._coerce(filter.where, options);
return filter;
};
@ -1555,15 +1582,19 @@ function coerceArray(val) {
/*
* Coerce values based the property types
* @param {Object} where The where clause
* @options {Object} [options] Optional options to use.
* @property {Boolean} allowExtendedOperators.
* @returns {Object} The coerced where clause
* @private
*/
DataAccessObject._coerce = function(where) {
DataAccessObject._coerce = function(where, options) {
var self = this;
if (!where) {
return where;
}
options = options || {};
var err;
if (typeof where !== 'object' || Array.isArray(where)) {
err = new Error(g.f('The where clause %j is not an {{object}}', where));
@ -1585,7 +1616,7 @@ DataAccessObject._coerce = function(where) {
}
for (var k = 0; k < clauses.length; k++) {
self._coerce(clauses[k]);
self._coerce(clauses[k], options);
}
continue;
@ -1703,8 +1734,7 @@ DataAccessObject._coerce = function(where) {
}
} else {
if (val != null) {
const dsSettings = this.getDataSource().settings;
const allowExtendedOperators = dsSettings.allowExtendedOperators;
const allowExtendedOperators = self._allowExtendedOperators(options);
if (operator === null && val instanceof RegExp) {
// Normalize {name: /A/} to {name: {regexp: /A/}}
operator = 'regexp';
@ -1831,7 +1861,7 @@ DataAccessObject.find = function find(query, options, cb) {
'all() must be implemented by the connector');
try {
this._normalize(query);
this._normalize(query, options);
} catch (err) {
process.nextTick(function() {
cb(err);
@ -2189,7 +2219,7 @@ DataAccessObject.destroyAll = function destroyAll(where, options, cb) {
try {
// Support an optional where object
where = removeUndefined(where);
where = Model._coerce(where);
where = Model._coerce(where, options);
} catch (err) {
return process.nextTick(function() {
cb(err);
@ -2345,7 +2375,7 @@ DataAccessObject.count = function(where, options, cb) {
try {
where = removeUndefined(where);
where = this._coerce(where);
where = this._coerce(where, options);
} catch (err) {
process.nextTick(function() {
cb(err);
@ -2616,9 +2646,9 @@ DataAccessObject.updateAll = function(where, data, options, cb) {
function doUpdate(where, data) {
try {
where = removeUndefined(where);
where = Model._coerce(where);
where = Model._coerce(where, options);
data = removeUndefined(data);
data = Model._coerce(data);
data = Model._coerce(data, options);
} catch (err) {
return process.nextTick(function() {
cb(err);
@ -3083,9 +3113,7 @@ function(data, options, cb) {
if (isPKMissing(Model, cb))
return cb.promise;
var allowExtendedOperators = connector.settings &&
connector.settings.allowExtendedOperators;
var allowExtendedOperators = Model._allowExtendedOperators(options);
var strict = this.__strict;
var model = Model.modelName;
var hookState = {};

View File

@ -8,32 +8,17 @@
const DataSource = require('..').DataSource;
const should = require('should');
describe('Model.settings.allowExtendedOperators', () => {
context('DAO.find()', () => {
it('converts extended operators to string value by default', () => {
const TestModel = createTestModel();
return TestModel.find(extendedQuery()).then((results) => {
should(results[0].value).eql('[object Object]');
});
});
it('preserves extended operators wit allowExtendedOperators set', () => {
const TestModel = createTestModel({allowExtendedOperators: true});
return TestModel.find(extendedQuery()).then((results) => {
should(results[0].value).eql({$exists: true});
});
});
function extendedQuery() {
// datasource modifies the query,
// we have to build a new object for each test
return {where: {value: {$exists: true}}};
}
});
function createTestModel(connectorSettings) {
describe('allowExtendedOperators', () => {
function createTestModel(connectorSettings, modelSettings) {
const ds = createTestDataSource(connectorSettings);
return ds.createModel('TestModel', {value: String});
const TestModel = ds.createModel('TestModel', {value: String}, modelSettings);
TestModel.observe('persist', function(ctx, next) {
ctx.Model.lastPersistedData = ctx.data;
next();
});
return TestModel;
}
function createTestDataSource(connectorSettings) {
@ -47,16 +32,309 @@ describe('Model.settings.allowExtendedOperators', () => {
return new DataSource(connectorSettings);
}
function extendedQuery() {
// datasource modifies the query,
// we have to build a new object for each test
return {where: {value: {$exists: true}}};
}
function setCustomData() {
return {$set: {value: 'changed'}};
}
function updateShouldHaveFailed() {
throw new Error('updateAttributes() should have failed.');
}
class TestConnector {
constructor(dataSource) {
}
create(model, data, options, callback) {
callback();
}
updateAttributes(model, id, data, options, callback) {
callback();
}
all(model, filter, options, callback) {
// return the raw "value" query
var instanceFound = {
let instanceFound = {
value: filter.where.value,
};
callback(null, [instanceFound]);
}
}
describe('dataSource.settings.allowExtendedOperators', () => {
context('DAO.find()', () => {
it('converts extended operators to string value by default', () => {
const TestModel = createTestModel();
return TestModel.find(extendedQuery()).then((results) => {
should(results[0].value).eql('[object Object]');
});
});
it('preserves extended operators with allowExtendedOperators set', () => {
const TestModel = createTestModel({allowExtendedOperators: true});
return TestModel.find(extendedQuery()).then((results) => {
should(results[0].value).eql({$exists: true});
});
});
it('`Model.settings.allowExtendedOperators` override data source settings - ' +
'converts extended operators', () => {
const TestModel = createTestModel({allowExtendedOperators: true}, {allowExtendedOperators: false});
return TestModel.find(extendedQuery()).then((results) => {
should(results[0].value).eql('[object Object]');
});
});
it('`Model.settings.allowExtendedOperators` override data source settings - ' +
'preserves extended operators', () => {
const TestModel = createTestModel({allowExtendedOperators: false}, {allowExtendedOperators: true});
return TestModel.find(extendedQuery()).then((results) => {
should(results[0].value).eql({$exists: true});
});
});
it('`options.allowExtendedOperators` override data source settings - ' +
'converts extended operators', () => {
const TestModel = createTestModel({allowExtendedOperators: true});
return TestModel.find(extendedQuery(), {allowExtendedOperators: false}).then((results) => {
should(results[0].value).eql('[object Object]');
});
});
it('`options.allowExtendedOperators` override data source settings - ' +
'preserves extended operators', () => {
const TestModel = createTestModel({allowExtendedOperators: false});
return TestModel.find(extendedQuery(), {allowExtendedOperators: true}).then((results) => {
should(results[0].value).eql({$exists: true});
});
});
});
context('DAO.updateAttributes()', () => {
it('`options.allowExtendedOperators` override data source settings - disable strict check', () => {
const TestModel = createTestModel({allowExtendedOperators: false}, {strict: true});
return TestModel.create({value: 'test'}).then((instance) => {
return instance.updateAttributes(setCustomData(), {allowExtendedOperators: true})
.then(() => {
should(TestModel.lastPersistedData).eql(setCustomData());
});
});
});
it('`options.allowExtendedOperators` override data source settings - enable strict check', () => {
const TestModel = createTestModel({allowExtendedOperators: true}, {strict: true});
return TestModel.create({value: 'test'}).then((inst) => {
return inst.updateAttributes(setCustomData(), {allowExtendedOperators: false})
.then(updateShouldHaveFailed, function onError(err) {
should.exist(err);
should(err.name).equal('ValidationError');
});
});
});
it('`Model.settings.allowExtendedOperators` override data source settings - ' +
'disable strict check', () => {
const TestModel = createTestModel({allowExtendedOperators: false},
{strict: true, allowExtendedOperators: true});
return TestModel.create({value: 'test'}).then((instance) => {
return instance.updateAttributes(setCustomData()).then(() => {
should(TestModel.lastPersistedData).eql(setCustomData());
});
});
});
it('`Model.settings.allowExtendedOperators` override data source settings - ' +
'enable strict check', () => {
const TestModel = createTestModel({allowExtendedOperators: true},
{strict: true, allowExtendedOperators: false});
return TestModel.create({value: 'test'}).then((inst) => {
return inst.updateAttributes(setCustomData())
.then(updateShouldHaveFailed, function onError(err) {
should.exist(err);
should(err.name).equal('ValidationError');
});
});
});
});
});
describe('Model.settings.allowExtendedOperators', () => {
context('DAO.find()', () => {
it('preserves extended operators with allowExtendedOperators set', () => {
const TestModel = createTestModel({}, {allowExtendedOperators: true});
return TestModel.find(extendedQuery()).then((results) => {
should(results[0].value).eql({$exists: true});
});
});
it('`dataSource.settings.allowExtendedOperators` honor Model settings - ' +
'converts extended operators', () => {
const TestModel = createTestModel({allowExtendedOperators: true}, {allowExtendedOperators: false});
return TestModel.find(extendedQuery()).then((results) => {
should(results[0].value).eql('[object Object]');
});
});
it('`dataSource.settings.allowExtendedOperators` honor Model settings - ' +
'preserves extended operators', () => {
const TestModel = createTestModel({allowExtendedOperators: false}, {allowExtendedOperators: true});
return TestModel.find(extendedQuery()).then((results) => {
should(results[0].value).eql({$exists: true});
});
});
it('`options.allowExtendedOperators` override Model settings - converts extended operators', () => {
const TestModel = createTestModel({allowExtendedOperators: true});
return TestModel.find(extendedQuery(), {allowExtendedOperators: false}).then((results) => {
should(results[0].value).eql('[object Object]');
});
});
it('`options.allowExtendedOperators` Model settings - preserves extended operators', () => {
const TestModel = createTestModel({allowExtendedOperators: false});
return TestModel.find(extendedQuery(), {allowExtendedOperators: true}).then((results) => {
should(results[0].value).eql({$exists: true});
});
});
});
context('DAO.updateAttributes()', () => {
it('`options.allowExtendedOperators` override Model settings - disable strict check', () => {
const TestModel = createTestModel({}, {strict: true, allowExtendedOperators: false});
return TestModel.create({value: 'test'}).then((instance) => {
return instance.updateAttributes(setCustomData(), {allowExtendedOperators: true})
.then(() => {
should(TestModel.lastPersistedData).eql(setCustomData());
});
});
});
it('`options.allowExtendedOperators` override Model settings - enabled strict check', () => {
const TestModel = createTestModel({}, {strict: true, allowExtendedOperators: true});
return TestModel.create({value: 'test'}).then((inst) => {
return inst.updateAttributes(setCustomData(), {allowExtendedOperators: false})
.then(updateShouldHaveFailed, function onError(err) {
should.exist(err);
should(err.name).equal('ValidationError');
});
});
});
it('`dataSource.settings.allowExtendedOperators` honor Model settings - disable strict check', () => {
const TestModel = createTestModel({allowExtendedOperators: false},
{strict: true, allowExtendedOperators: true});
return TestModel.create({value: 'test'}).then((instance) => {
return instance.updateAttributes(setCustomData()).then(() => {
should(TestModel.lastPersistedData).eql(setCustomData());
});
});
});
it('`dataSource.settings.allowExtendedOperators` honor Model settings - ' +
'enable strict check', () => {
const TestModel = createTestModel({allowExtendedOperators: true},
{strict: true, allowExtendedOperators: false});
return TestModel.create({value: 'test'}).then((inst) => {
return inst.updateAttributes(setCustomData())
.then(updateShouldHaveFailed, function onError(err) {
should.exist(err);
should(err.name).equal('ValidationError');
});
});
});
});
});
describe('options.allowExtendedOperators', () => {
context('DAO.find()', () => {
it('preserves extended operators with allowExtendedOperators set', () => {
const TestModel = createTestModel();
return TestModel.find(extendedQuery(), {allowExtendedOperators: true}).then((results) => {
should(results[0].value).eql({$exists: true});
});
});
it('`dataSource.settings.allowExtendedOperators` honor options settings - ' +
'converts extended operators', () => {
const TestModel = createTestModel({allowExtendedOperators: true});
return TestModel.find(extendedQuery(), {allowExtendedOperators: false}).then((results) => {
should(results[0].value).eql('[object Object]');
});
});
it('`dataSource.settings.allowExtendedOperators` honor options settings - ' +
'preserves extended operators', () => {
const TestModel = createTestModel({allowExtendedOperators: false});
return TestModel.find(extendedQuery(), {allowExtendedOperators: true}).then((results) => {
should(results[0].value).eql({$exists: true});
});
});
it('`Model.settings.allowExtendedOperators` honor options settings - ' +
'converts extended operators', () => {
const TestModel = createTestModel({}, {allowExtendedOperators: true});
return TestModel.find(extendedQuery(), {allowExtendedOperators: false}).then((results) => {
should(results[0].value).eql('[object Object]');
});
});
it('`Model.settings.allowExtendedOperators` honor options settings - ' +
'preserves extended operators', () => {
const TestModel = createTestModel({}, {allowExtendedOperators: false});
return TestModel.find(extendedQuery(), {allowExtendedOperators: true}).then((results) => {
should(results[0].value).eql({$exists: true});
});
});
});
context('DAO.updateAttributes()', () => {
it('`Model.settings.allowExtendedOperators` honor options settings - disable strict check', () => {
const TestModel = createTestModel({}, {strict: true, allowExtendedOperators: false});
return TestModel.create({value: 'test'}).then((instance) => {
return instance.updateAttributes(setCustomData(), {allowExtendedOperators: true})
.then(() => {
should(TestModel.lastPersistedData).eql(setCustomData());
});
});
});
it('`Model.settings.allowExtendedOperators` honor options settings - enable strict check', () => {
const TestModel = createTestModel({}, {strict: true, allowExtendedOperators: true});
return TestModel.create({value: 'test'}).then((inst) => {
return inst.updateAttributes(setCustomData(), {allowExtendedOperators: false})
.then(updateShouldHaveFailed, function onError(err) {
should.exist(err);
should(err.name).equal('ValidationError');
});
});
});
it('`dataSource.settings.allowExtendedOperators` honor options settings - disable strict check', () => {
const TestModel = createTestModel({}, {strict: true});
return TestModel.create({value: 'test'}).then((instance) => {
return instance.updateAttributes(setCustomData(), {allowExtendedOperators: true})
.then(() => {
should(TestModel.lastPersistedData).eql(setCustomData());
});
});
});
it('`dataSource.settings.allowExtendedOperators` honor options settings - enable strict check', () => {
const TestModel = createTestModel({allowExtendedOperators: true}, {strict: true});
return TestModel.create({value: 'test'}).then((inst) => {
return inst.updateAttributes(setCustomData(), {allowExtendedOperators: false})
.then(updateShouldHaveFailed, function onError(err) {
should.exist(err);
should(err.name).equal('ValidationError');
});
});
});
});
});
});