不要怂,就是干,撸起袖子干!

Commit 24efbfda by Jan Aagaard Meier

feat(scopes) Support and test for scope in count. Slight changes to injectScope

1 parent 208d865b
# Next # Next
- [FEATURE] Lock modes in Postgres now support `OF table` - [FEATURE] Lock modes in Postgres now support `OF table`
- [FEATURE] New transaction lock modes `FOR KEY SHARE` and `NO KEY UPDATE` for Postgres 9.3+ - [FEATURE] New transaction lock modes `FOR KEY SHARE` and `NO KEY UPDATE` for Postgres 9.3+
- [FEATURE/REFACTOR] Rewritten scopes with complete support for includes and scopes across associations
# 2.1.0 # 2.1.0
- [BUG] Enable standards conforming strings on connection in postgres. Adresses [#3545](https://github.com/sequelize/sequelize/issues/3545) - [BUG] Enable standards conforming strings on connection in postgres. Adresses [#3545](https://github.com/sequelize/sequelize/issues/3545)
......
...@@ -77,7 +77,7 @@ module.exports = (function() { ...@@ -77,7 +77,7 @@ module.exports = (function() {
updateWhere = {}; updateWhere = {};
updateWhere[primaryKey] = obsoleteIds; updateWhere[primaryKey] = obsoleteIds;
promises.push(this.association.target.update( promises.push(this.association.target.unscoped().update(
update, update,
Utils._.extend(options, { Utils._.extend(options, {
allowNull: [self.association.identifier], allowNull: [self.association.identifier],
......
...@@ -191,7 +191,7 @@ module.exports = (function() { ...@@ -191,7 +191,7 @@ module.exports = (function() {
return self.primaryKeyAttributes.indexOf(key) !== -1; return self.primaryKeyAttributes.indexOf(key) !== -1;
}); });
this.__scope = _.isPlainObject(this.options.defaultScope) ? this.options.defaultScope : {}; this.$scope = _.isPlainObject(this.options.defaultScope) ? this.options.defaultScope : {};
_.each(this.options.scopes, function (scope) { _.each(this.options.scopes, function (scope) {
if (_.isPlainObject(scope) && scope.include) { if (_.isPlainObject(scope) && scope.include) {
...@@ -510,7 +510,15 @@ module.exports = (function() { ...@@ -510,7 +510,15 @@ module.exports = (function() {
* }, * },
* complexFunction: function(email, accessLevel) { * complexFunction: function(email, accessLevel) {
* return { * return {
* where: ['email like ? AND access_level >= ?', email + '%', accessLevel] * where: {
email: {
$like: email
},
accesss_level {
$gte: accessLevel
}
}
}
* } * }
* }, * },
* } * }
...@@ -528,7 +536,7 @@ module.exports = (function() { ...@@ -528,7 +536,7 @@ module.exports = (function() {
* // WHERE email like 'dan@sequelize.com%' AND access_level >= 42 * // WHERE email like 'dan@sequelize.com%' AND access_level >= 42
* ``` * ```
* *
* @param {Array|Object|String|null} options* The scope(s) to apply. Scopes can either be passed as consecutive arguments, or as an array of arguments. To apply simple scopes, pass them as strings. For scope function, pass an object, with a `method` property. The value can either be a string, if the method does not take any arguments, or an array, where the first element is the name of the method, and consecutive elements are arguments to that method. Pass null to remove all scopes, including the default. * @param {Array|Object|String|null} options* The scope(s) to apply. Scopes can either be passed as consecutive arguments, or as an array of arguments. To apply simple scopes and scope functions with no arguments, pass them as strings. For scope function, pass an object, with a `method` property. The value can either be a string, if the method does not take any arguments, or an array, where the first element is the name of the method, and consecutive elements are arguments to that method. Pass null to remove all scopes, including the default.
* @return {Model} A reference to the model, with the scope(s) applied. Calling scope again on the returned model will clear the previous scope. * @return {Model} A reference to the model, with the scope(s) applied. Calling scope again on the returned model will clear the previous scope.
*/ */
Model.prototype.scope = function(option) { Model.prototype.scope = function(option) {
...@@ -537,7 +545,7 @@ module.exports = (function() { ...@@ -537,7 +545,7 @@ module.exports = (function() {
, scope , scope
, scopeName; , scopeName;
self.__scope = {}; self.$scope = {};
if (!option) { if (!option) {
return self; return self;
...@@ -575,7 +583,7 @@ module.exports = (function() { ...@@ -575,7 +583,7 @@ module.exports = (function() {
} }
if (!!scope) { if (!!scope) {
_.assign(self.__scope, scope, function scopeCustomizer(objectValue, sourceValue, key) { _.assign(self.$scope, scope, function scopeCustomizer(objectValue, sourceValue, key) {
if (key === 'where') { if (key === 'where') {
return _.assign(objectValue || {}, sourceValue); return _.assign(objectValue || {}, sourceValue);
} else if (key === 'include' && Array.isArray(objectValue) && Array.isArray(sourceValue)) { } else if (key === 'include' && Array.isArray(objectValue) && Array.isArray(sourceValue)) {
...@@ -691,7 +699,7 @@ module.exports = (function() { ...@@ -691,7 +699,7 @@ module.exports = (function() {
return Promise.bind(this).then(function() { return Promise.bind(this).then(function() {
conformOptions(options, this); conformOptions(options, this);
this.__injectScope(options); Model.$injectScope(this.$scope, options);
if (options.hooks) { if (options.hooks) {
return this.runHooks('beforeFind', options); return this.runHooks('beforeFind', options);
...@@ -776,7 +784,7 @@ module.exports = (function() { ...@@ -776,7 +784,7 @@ module.exports = (function() {
options = optClone(param); options = optClone(param);
} }
this.__injectScope(options); Model.$injectScope(this.$scope, options);
if (options.limit === undefined && !(options.where && options.where[this.primaryKeyAttribute])) { if (options.limit === undefined && !(options.where && options.where[this.primaryKeyAttribute])) {
options.limit = 1; options.limit = 1;
...@@ -845,6 +853,7 @@ module.exports = (function() { ...@@ -845,6 +853,7 @@ module.exports = (function() {
Model.prototype.count = function(options) { Model.prototype.count = function(options) {
options = Utils._.clone(options || {}); options = Utils._.clone(options || {});
conformOptions(options, this); conformOptions(options, this);
Model.$injectScope(this.$scope, options);
var col = '*'; var col = '*';
if (options.include) { if (options.include) {
...@@ -1464,7 +1473,7 @@ module.exports = (function() { ...@@ -1464,7 +1473,7 @@ module.exports = (function() {
}, options || {}); }, options || {});
options.type = QueryTypes.BULKDELETE; options.type = QueryTypes.BULKDELETE;
this.__injectScope(options); Model.$injectScope(this.$scope, options);
Utils.mapOptionFieldNames(options, this); Utils.mapOptionFieldNames(options, this);
...@@ -1612,7 +1621,7 @@ module.exports = (function() { ...@@ -1612,7 +1621,7 @@ module.exports = (function() {
options.type = QueryTypes.BULKUPDATE; options.type = QueryTypes.BULKUPDATE;
this.__injectScope(options); Model.$injectScope(this.$scope, options);
// Remove values that are not in the options.fields // Remove values that are not in the options.fields
if (options.fields && options.fields instanceof Array) { if (options.fields && options.fields instanceof Array) {
...@@ -1780,15 +1789,19 @@ module.exports = (function() { ...@@ -1780,15 +1789,19 @@ module.exports = (function() {
}; };
// Inject current scope into options. Includes should have been conformed (conformOptions) before calling this // Inject current scope into options. Includes should have been conformed (conformOptions) before calling this
Model.prototype.__injectScope = function (options) { Model.$injectScope = function (scope, options) {
_.defaults(options, this.__scope); var filteredScope = _.omit(scope, 'include'); // Includes need special treatment
_.defaults(options.where, this.__scope.where);
_.defaults(options, filteredScope);
if (options.include && this.__scope.include) { _.defaults(options.where, filteredScope.where);
this.__scope.include.forEach(function (scopeInclude) {
if (!_.any(options.include, function (optionInclude) { if (scope.include) {
return optionInclude.model.name === scopeInclude.model.name; options.include = options.include || [];
})) {
// Reverse so we consider the latest include first.
// This is used if several scopes specify the same include - the last scope should take precendence
scope.include.reverse().forEach(function (scopeInclude) {
if (!_.any(options.include, _.matchesDots('model.name', scopeInclude.model.name))) {
options.include.push(scopeInclude); options.include.push(scopeInclude);
} }
}); });
...@@ -1961,7 +1974,7 @@ module.exports = (function() { ...@@ -1961,7 +1974,7 @@ module.exports = (function() {
} }
if (!include.all) { if (!include.all) {
_.defaults(include, model.__scope); _.defaults(include, model.$scope);
} }
return include; return include;
......
...@@ -6,6 +6,7 @@ var DataTypes = require('./data-types') ...@@ -6,6 +6,7 @@ var DataTypes = require('./data-types')
, ParameterValidator = require('./utils/parameter-validator') , ParameterValidator = require('./utils/parameter-validator')
, inflection = require('inflection') , inflection = require('inflection')
, _ = require('lodash') , _ = require('lodash')
, dottie = require('dottie')
, uuid = require('node-uuid'); , uuid = require('node-uuid');
var Utils = module.exports = { var Utils = module.exports = {
...@@ -52,6 +53,11 @@ var Utils = module.exports = { ...@@ -52,6 +53,11 @@ var Utils = module.exports = {
} }
} }
return result; return result;
},
matchesDots: function (dots, value) {
return function (item) {
return dottie.get(item, dots) === value;
};
} }
}); });
......
...@@ -91,7 +91,7 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -91,7 +91,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
}).spread(function (u1, u2, u3, u4, c1, c2, u5, proj1, prof1) { }).spread(function (u1, u2, u3, u4, c1, c2, u5, proj1, prof1) {
return Promise.all([ return Promise.all([
c1.setUsers([u1, u2, u3, u4]), c1.setUsers([u1, u2, u3, u4]),
c2.setUsers([u5]) c2.setUsers([u5]),
]); ]);
}); });
}); });
......
'use strict';
/* jshint -W030 */
/* jshint -W110 */
var chai = require('chai')
, Sequelize = require('../../../../index')
, expect = chai.expect
, Support = require(__dirname + '/../../support');
describe(Support.getTestDialectTeaser('Model'), function() {
describe('scope', function () {
describe('count', function () {
beforeEach(function () {
this.ScopeMe = this.sequelize.define('ScopeMe', {
username: Sequelize.STRING,
email: Sequelize.STRING,
access_level: Sequelize.INTEGER,
other_value: Sequelize.INTEGER
}, {
defaultScope: {
where: {
access_level: {
gte: 5
}
}
},
scopes: {
lowAccess: {
where: {
access_level: {
lte: 5
}
}
}
}
});
return this.sequelize.sync({force: true}).then(function() {
var records = [
{username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7},
{username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11},
{username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10},
{username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7}
];
return this.ScopeMe.bulkCreate(records);
}.bind(this));
});
it('should apply defaultScope', function () {
return expect(this.ScopeMe.count()).to.eventually.equal(2);
});
it('should be able to override default scope', function () {
return expect(this.ScopeMe.count({ where: { access_level: { gt: 5 }}})).to.eventually.equal(1);
});
it('should be able to unscope', function () {
return expect(this.ScopeMe.unscoped().count()).to.eventually.equal(4);
});
it('should be able to apply other scopes', function () {
return expect(this.ScopeMe.scope('lowAccess').count()).to.eventually.equal(3);
});
it('should be able to merge scopes with where', function () {
return expect(this.ScopeMe.scope('lowAccess').count({ where: { username: 'dan'}})).to.eventually.equal(1);
});
});
});
});
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!