Sequelize Scopes 部分源碼解析

前言

在使用SequelizeScopes的時候,遇到了一些坑,也很好奇裏面怎樣實現的,因此特地看一遍源碼~~數組

定義

定義方式有兩種:app

第一種

Sequelize.define(modelName, attributes, options)設置options.defaultScopeoptions.scopeside

// 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) 定義並檢查ui

// 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 = truethis

// 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)等等spa

// 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,只能是 object 定義
      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()code

// lib/model.js
static unscoped() {
  return this.scope();
}
複製代碼

那麼其實設置了scopes以後,在查詢中如何體現呢?orm

// lib/model.js
static findAll(options) {
  ...
  // 這個方法他會將 Model._scope 合併到 options 裏面
  this._injectScope(options);
  ...
}
複製代碼

總結

根據以上源碼能夠知道,ip

  • 調用Model.scope(option)以後的Model是一個全功能的Model,只是修改了Model._scope,以前怎麼用如今也能夠同樣用,包括在scope.include裏面使用
  • 咱們能夠經過兩種方法定義scope,若是在Sequelize.define(modelName, attributes, options) 不方便定義defaultScope的時候,能夠經過Model.addScope(name, scope, { override: true})覆蓋
  • 若是定義defaultScope,只能定義Object
  • scope合併規則是key後者覆蓋前者
  • attributesincludegroup,不會覆蓋,只會concat鏈接
  • where也不會被覆蓋,where裏面的key會後者覆蓋前者
  • 若是使用多個scope,合併的時候須要注意參數順序,省得發生預料以外的合併結果
  • 若是不須要scope,調用Model.scope()或者Model.unscope()便可去除

博客原文地址:Sequelize Scopes 部分源碼解析underscore

相關文章
相關標籤/搜索