前言
在使用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 });
// 重置 _scope
self._scope = {};
self._scopeNames = [];
self.scoped = true;
// 如果 option 真值為 false,就去除任何 scope,包括 defaultScope
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;
}
複製程式碼
值得一提是,Model.scope(option)
引數真值為false
即去除所有scope
,包括defaultScope
,也可以通過呼叫unscoped()
static unscoped() {
return this.scope();
}
複製程式碼
那麼其實設定了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
,合併的時候需要注意引數順序,免得發生預料之外的合併結果 - 如果不需要
scope
,呼叫Model.scope()
或者Model.unscope()
即可去除
部落格原文地址:Sequelize Scopes 部分原始碼解析