前言
在使用 Sequelize 的 Scopes 的时候, 遇到了一些坑, 也很好奇里面怎样实现的, 所以特意看一遍源码~~
定义
定义方式有两种:
第一种
在
Sequelize.define(modelName, attributes, options)
设置
options.defaultScope
和 options.scopes
- // lib/sequelize.js
- define(modelName, attributes, options) {
- options = options || {};
- options.modelName = modelName;
- options.sequelize = this;
- const model = class extends Model {};
- // 这里能看到实际上定义的方法是在 model 里面
- model.init(attributes, options);
- return model;
- }
实际上是从
Model.init(attributes, options)
定义并检查
- // lib/model.js
- static init(attributes, options) { // testhint options:none
- ...
- // 初始化 defaultScope 和 scopes
- this.options = Object.assign({
- timestamps: true,
- validate: {},
- freezeTableName: false,
- underscored: false,
- underscoredAll: false,
- paranoid: false,
- rejectOnEmpty: false,
- whereCollection: null,
- schema: null,
- schemaDelimiter: '',
- defaultScope: {},
- scopes: [],
- indexes: []
- }, options);
- ...
- // 赋值 _scope 为 defaultScope
- this._scope = this.options.defaultScope;
- this._scopeNames = ['defaultScope'];
- // 检查 scope 内属性
- if (_.isPlainObject(this._scope)) {
- this._conformOptions(this._scope, this);
- }
- _.each(this.options.scopes, scope => {
- if (_.isPlainObject(scope)) {
- this._conformOptions(scope, this);
- }
- });
- ...
- return this;
- }
第二种
从
Model.addScope(name, scope, options)
增加, 如果添加的是重复的或者是 defaultScope 则需要
- options.override = true
- // lib/model.js
- static addScope(name, scope, options) {
- options = _.assign({
- override: false
- }, options);
- // 如果添加的是重复 scope 或者是 defaultScope 则需要 options.override = true, 否则抛异常
- if ((name === 'defaultScope' || name in this.options.scopes) && options.override === false) {
- throw new Error('The scope' + name + 'already exists. Pass { override: true } as options to silence this error');
- }
- // 同样地, 检查 scope 内属性
- this._conformOptions(scope, this);
- // 如果是 defaultScope 还要赋值 _scope
- if (name === 'defaultScope') {
- this.options.defaultScope = this._scope = scope;
- } else {
- this.options.scopes[name] = scope;
- }
- }
使用
scope 通过
Model.scope(option)
方法调用, 该方法可以传入一个或者多个 scope 名称或者方法, 并返回一个全功能的 Model, 可以调用原来 Model 的所有方法, 如:
- Model.findAll(options)
- ,
- Model.update(values, options)
- ,
- Model.count(options)
- ,
- Model.destroy(options)
等等
- // lib/model.js
- static scope(option) {
- const self = class extends this {};
- let scope;
- let scopeName;
- Object.defineProperty(self, 'name', { value: this.name });
- self._scope = {};
- self._scopeNames = [];
- self.scoped = true;
- if (!option) {
- return self;
- }
- // 通过 lodash 展平一级数据嵌套, 兼容数组传入或者多参数传入
- const options = _.flatten(arguments);
- // 开始循环处理每一个 scope
- for (const option of options) {
- scope = null;
- scopeName = null;
- if (_.isPlainObject(option)) {
- // 处理通过 { method: [ 'scopeName', params] }
- if (option.method) {
- if (Array.isArray(option.method) && !!self.options.scopes[option.method[0]]) {
- scopeName = option.method[0];
- // 传入参数调用定义的 scope function
- scope = self.options.scopes[scopeName].apply(self, option.method.slice(1));
- }
- else if (self.options.scopes[option.method]) {
- scopeName = option.method;
- scope = self.options.scopes[scopeName].apply(self);
- }
- } else {
- // 或者你也可以直接传入一个 scope 实例, 如:{attributes, include}
- scope = option;
- }
- } else {
- // 处理 string 方式传入
- // 如果是 defaultScope 接引入用
- if (option === 'defaultScope' && _.isPlainObject(self.options.defaultScope)) {
- scope = self.options.defaultScope;
- } else {
- scopeName = option;
- // 获取对应 object
- scope = self.options.scopes[scopeName];
- // 如果是 function 即调用引用
- if (_.isFunction(scope)) {
- scope = scope();
- this._conformOptions(scope, self);
- }
- }
- }
- // 如果有值, 开始处理 scope 合并
- if (scope) {
- _.assignWith(self._scope, scope, (objectValue, sourceValue, key) => {
- // 如果是 where assign 合并, 后者替代前者
- if (key === 'where') {
- return Array.isArray(sourceValue) ? sourceValue : Object.assign(objectValue || {}, sourceValue);
- // 如果是 attributes, include, group 其中之一, 则 concat 连接
- } else if (['attributes', 'include', 'group'].indexOf(key)>= 0 && Array.isArray(objectValue) && Array.isArray(sourceValue)) {
- return objectValue.concat(sourceValue);
- }
- // 其他情况直接 替换
- return objectValue ? objectValue : sourceValue;
- });
- self._scopeNames.push(scopeName ? scopeName : 'defaultScope');
- } else {
- throw new sequelizeErrors.SequelizeScopeError('Invalid scope' + scopeName + 'called.');
- }
- }
- return self;
- }
那么其实设置了 scopes 之后, 在查询中如何体现呢?
- // lib/model.js
- static findAll(options) {
- ...
- // 这个方法他会将 Model._scope 合并到 options 里面
- this._injectScope(options);
- ...
- }
总结
根据以上源码可以知道,
调用
Model.scope(option)
之后的 Model 是一个全功能的 Model, 只是修改了 Model._scope, 之前怎么用现在也可以一样用, 包括在 scope.include 里面使用
我们可以通过两种方法定义 scope, 如果在
Sequelize.define(modelName, attributes, options)
不方便定义 defaultScope 的时候, 可以通过
Model.addScope(name, scope, { override: true})
覆盖
where 的 key 会覆盖
attributes,include 和 group, 不会覆盖, 只会 concat 连接
如果使用多个 scope, 合并的时候需要注意参数顺序, 免得发生预料之外的合并结果
来源: https://juejin.im/post/5b2b7a4ae51d4558c91ba4c9