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

Commit 586a57a4 by Felix Becker Committed by Jan Aagaard Meier

Clean up Model.init() API (#6437)

* Remove Model.prototype.modelManager

* Move documentation from Sequelize.define to Model.init

* Move logic from Sequelize.define to Model.init

* Add changelog entry
1 parent 23f1316d
# Future # Future
- [CHANGED] Removed `modelManager` parameter from `Model.init()` [#6437](https://github.com/sequelize/sequelize/issues/6437)
- [FIXED] Made `Model.init()` behave like `sequelize.define()` (hooks are called and options have proper defaults) [#6437](https://github.com/sequelize/sequelize/issues/6437)
- [ADDED] `restartIdentity` option for truncate in postgres [#5356](https://github.com/sequelize/sequelize/issues/5356) - [ADDED] `restartIdentity` option for truncate in postgres [#5356](https://github.com/sequelize/sequelize/issues/5356)
- [INTERNAL] Migrated to `node-mysql2` for prepared statements [#6354](https://github.com/sequelize/sequelize/issues/6354) - [INTERNAL] Migrated to `node-mysql2` for prepared statements [#6354](https://github.com/sequelize/sequelize/issues/6354)
- [ADDED] SQLCipher support via the SQLite connection manager - [ADDED] SQLCipher support via the SQLite connection manager
......
...@@ -57,7 +57,7 @@ class BelongsToMany extends Association { ...@@ -57,7 +57,7 @@ class BelongsToMany extends Association {
this.target = target; this.target = target;
this.targetAssociation = null; this.targetAssociation = null;
this.options = options; this.options = options;
this.sequelize = source.modelManager.sequelize; this.sequelize = source.sequelize;
this.through = _.assign({}, options.through); this.through = _.assign({}, options.through);
this.scope = options.scope; this.scope = options.scope;
this.isMultiAssociation = true; this.isMultiAssociation = true;
......
...@@ -21,7 +21,7 @@ class HasMany extends Association { ...@@ -21,7 +21,7 @@ class HasMany extends Association {
this.target = target; this.target = target;
this.targetAssociation = null; this.targetAssociation = null;
this.options = options || {}; this.options = options || {};
this.sequelize = source.modelManager.sequelize; this.sequelize = source.sequelize;
this.through = options.through; this.through = options.through;
this.scope = options.scope; this.scope = options.scope;
this.isMultiAssociation = true; this.isMultiAssociation = true;
......
...@@ -27,7 +27,7 @@ function addForeignKeyConstraints(newAttribute, source, target, options, key) { ...@@ -27,7 +27,7 @@ function addForeignKeyConstraints(newAttribute, source, target, options, key) {
if (primaryKeys.length === 1) { if (primaryKeys.length === 1) {
if (!!source._schema) { if (!!source._schema) {
newAttribute.references = { newAttribute.references = {
model: source.modelManager.sequelize.queryInterface.QueryGenerator.addSchema({ model: source.sequelize.queryInterface.QueryGenerator.addSchema({
tableName: source.tableName, tableName: source.tableName,
_schema: source._schema, _schema: source._schema,
_schemaDelimiter: source._schemaDelimiter _schemaDelimiter: source._schemaDelimiter
......
...@@ -39,7 +39,7 @@ const defaultsOptions = { raw: true }; ...@@ -39,7 +39,7 @@ const defaultsOptions = { raw: true };
class Model { class Model {
static get QueryInterface() { static get QueryInterface() {
return this.modelManager.sequelize.getQueryInterface(); return this.sequelize.getQueryInterface();
} }
static get QueryGenerator() { static get QueryGenerator() {
...@@ -563,9 +563,134 @@ class Model { ...@@ -563,9 +563,134 @@ class Model {
}); });
} }
static init(attributes, options, modelManager) { // testhint options:none /**
* Initialize a model, representing a table in the DB, with attributes and options.
*
* The table columns are define by the hash that is given as the second argument. Each attribute of the hash represents a column. A short table definition might look like this:
*
* ```js
* Project.init({
* columnA: {
* type: Sequelize.BOOLEAN,
* validate: {
* is: ['[a-z]','i'], // will only allow letters
* max: 23, // only allow values <= 23
* isIn: {
* args: [['en', 'zh']],
* msg: "Must be English or Chinese"
* }
* },
* field: 'column_a'
* // Other attributes here
* },
* columnB: Sequelize.STRING,
* columnC: 'MY VERY OWN COLUMN TYPE'
* }, {sequelize})
*
* sequelize.models.modelName // The model will now be available in models under the class name
* ```
*
*
* As shown above, column definitions can be either strings, a reference to one of the datatypes that are predefined on the Sequelize constructor, or an object that allows you to specify both the type of the column, and other attributes such as default values, foreign key constraints and custom setters and getters.
*
* For a list of possible data types, see http://docs.sequelizejs.com/en/latest/docs/models-definition/#data-types
*
* For more about getters and setters, see http://docs.sequelizejs.com/en/latest/docs/models-definition/#getters-setters
*
* For more about instance and class methods, see http://docs.sequelizejs.com/en/latest/docs/models-definition/#expansion-of-models
*
* For more about validation, see http://docs.sequelizejs.com/en/latest/docs/models-definition/#validations
*
* @see {DataTypes}
* @see {Hooks}
* @param {Object} attributes An object, where each attribute is a column of the table. Each column can be either a DataType, a string or a type-description object, with the properties described below:
* @param {String|DataType|Object} attributes.column The description of a database column
* @param {String|DataType} attributes.column.type A string or a data type
* @param {Boolean} [attributes.column.allowNull=true] If false, the column will have a NOT NULL constraint, and a not null validation will be run before an instance is saved.
* @param {Any} [attributes.column.defaultValue=null] A literal default value, a JavaScript function, or an SQL function (see `sequelize.fn`)
* @param {String|Boolean} [attributes.column.unique=false] If true, the column will get a unique constraint. If a string is provided, the column will be part of a composite unique index. If multiple columns have the same string, they will be part of the same unique index
* @param {Boolean} [attributes.column.primaryKey=false]
* @param {String} [attributes.column.field=null] If set, sequelize will map the attribute name to a different name in the database
* @param {Boolean} [attributes.column.autoIncrement=false]
* @param {String} [attributes.column.comment=null]
* @param {String|Model} [attributes.column.references=null] An object with reference configurations
* @param {String|Model} [attributes.column.references.model] If this column references another table, provide it here as a Model, or a string
* @param {String} [attributes.column.references.key='id'] The column of the foreign table that this column references
* @param {String} [attributes.column.onUpdate] What should happen when the referenced key is updated. One of CASCADE, RESTRICT, SET DEFAULT, SET NULL or NO ACTION
* @param {String} [attributes.column.onDelete] What should happen when the referenced key is deleted. One of CASCADE, RESTRICT, SET DEFAULT, SET NULL or NO ACTION
* @param {Function} [attributes.column.get] Provide a custom getter for this column. Use `this.getDataValue(String)` to manipulate the underlying values.
* @param {Function} [attributes.column.set] Provide a custom setter for this column. Use `this.setDataValue(String, Value)` to manipulate the underlying values.
* @param {Object} [attributes.validate] An object of validations to execute for this column every time the model is saved. Can be either the name of a validation provided by validator.js, a validation function provided by extending validator.js (see the `DAOValidator` property for more details), or a custom validation function. Custom validation functions are called with the value of the field, and can possibly take a second callback argument, to signal that they are asynchronous. If the validator is sync, it should throw in the case of a failed validation, it it is async, the callback should be called with the error text.
* @param {Object} options These options are merged with the default define options provided to the Sequelize constructor
* @param {Object} options.sequelize The Sequelize connection to use. Required
* @param {Object} [options.defaultScope={}] Define the default search scope to use for this model. Scopes have the same form as the options passed to find / findAll
* @param {Object} [options.scopes] More scopes, defined in the same way as defaultScope above. See `Model.scope` for more information about how scopes are defined, and what you can do with them
* @param {Boolean} [options.omitNull] Don't persist null values. This means that all columns with null values will not be saved
* @param {Boolean} [options.timestamps=true] Adds createdAt and updatedAt timestamps to the model.
* @param {Boolean} [options.paranoid=false] Calling `destroy` will not delete the model, but instead set a `deletedAt` timestamp if this is true. Needs `timestamps=true` to work
* @param {Boolean} [options.underscored=false] Converts all camelCased columns to underscored if true
* @param {Boolean} [options.underscoredAll=false] Converts camelCased model names to underscored table names if true
* @param {Boolean} [options.freezeTableName=false] If freezeTableName is true, sequelize will not try to alter the DAO name to get the table name. Otherwise, the model name will be pluralized
* @param {Object} [options.name] An object with two attributes, `singular` and `plural`, which are used when this model is associated to others.
* @param {String} [options.name.singular=Utils.singularize(modelName)]
* @param {String} [options.name.plural=Utils.pluralize(modelName)]
* @param {Array<Object>} [options.indexes]
* @param {String} [options.indexes[].name] The name of the index. Defaults to model name + _ + fields concatenated
* @param {String} [options.indexes[].type] Index type. Only used by mysql. One of `UNIQUE`, `FULLTEXT` and `SPATIAL`
* @param {String} [options.indexes[].method] The method to create the index by (`USING` statement in SQL). BTREE and HASH are supported by mysql and postgres, and postgres additionally supports GIST and GIN.
* @param {Boolean} [options.indexes[].unique=false] Should the index by unique? Can also be triggered by setting type to `UNIQUE`
* @param {Boolean} [options.indexes[].concurrently=false] PostgreSQL will build the index without taking any write locks. Postgres only
* @param {Array<String|Object>} [options.indexes[].fields] An array of the fields to index. Each field can either be a string containing the name of the field, a sequelize object (e.g `sequelize.fn`), or an object with the following attributes: `attribute` (field name), `length` (create a prefix index of length chars), `order` (the direction the column should be sorted in), `collate` (the collation (sort order) for the column)
* @param {String|Boolean} [options.createdAt] Override the name of the createdAt column if a string is provided, or disable it if false. Timestamps must be true. Not affected by underscored setting.
* @param {String|Boolean} [options.updatedAt] Override the name of the updatedAt column if a string is provided, or disable it if false. Timestamps must be true. Not affected by underscored setting.
* @param {String|Boolean} [options.deletedAt] Override the name of the deletedAt column if a string is provided, or disable it if false. Timestamps must be true. Not affected by underscored setting.
* @param {String} [options.tableName] Defaults to pluralized model name, unless freezeTableName is true, in which case it uses model name verbatim
* @param {String} [options.schema='public']
* @param {String} [options.engine]
* @param {String} [options.charset]
* @param {String} [options.comment]
* @param {String} [options.collate]
* @param {String} [options.initialAutoIncrement] Set the initial AUTO_INCREMENT value for the table in MySQL.
* @param {Object} [options.hooks] An object of hook function that are called before and after certain lifecycle events. The possible hooks are: beforeValidate, afterValidate, validationFailed, beforeBulkCreate, beforeBulkDestroy, beforeBulkUpdate, beforeCreate, beforeDestroy, beforeUpdate, afterCreate, afterDestroy, afterUpdate, afterBulkCreate, afterBulkDestory and afterBulkUpdate. See Hooks for more information about hook functions and their signatures. Each property can either be a function, or an array of functions.
* @param {Object} [options.validate] An object of model wide validations. Validations have access to all model values via `this`. If the validator function takes an argument, it is assumed to be async, and is called with a callback that accepts an optional error.
*
* @return {Model}
*/
static init(attributes, options) { // testhint options:none
options = options || {};
if (!options.sequelize) {
throw new Error('No Sequelize instance passed');
}
this.sequelize = options.sequelize;
this.options = Utils._.extend({ const globalOptions = this.sequelize.options;
if (globalOptions.define) {
options = Utils.merge(globalOptions.define, options);
}
if (!options.modelName) {
options.modelName = this.name;
}
options = Utils.merge({
name: {
plural: Utils.pluralize(options.modelName),
singular: Utils.singularize(options.modelName)
},
indexes: [],
omitNul: globalOptions.omitNull
}, options);
this.sequelize.runHooks('beforeDefine', attributes, options);
if (options.modelName !== this.name) {
Object.defineProperty(this, 'name', {value: options.modelName});
}
delete options.modelName;
this.options = Object.assign({
timestamps: true, timestamps: true,
validate: {}, validate: {},
freezeTableName: false, freezeTableName: false,
...@@ -580,16 +705,19 @@ class Model { ...@@ -580,16 +705,19 @@ class Model {
scopes: [], scopes: [],
hooks: {}, hooks: {},
indexes: [] indexes: []
}, options || {}); }, options);
// if you call "define" multiple times for the same modelName, do not clutter the factory
if (this.sequelize.isDefined(this.name)) {
this.sequelize.modelManager.removeModel(this.sequelize.modelManager.getModel(this.name));
}
this.associations = {}; this.associations = {};
this.modelManager = null;
this.options.hooks = _.mapValues(this.replaceHookAliases(this.options.hooks), hooks => { this.options.hooks = _.mapValues(this.replaceHookAliases(this.options.hooks), hooks => {
if (!Array.isArray(hooks)) hooks = [hooks]; if (!Array.isArray(hooks)) hooks = [hooks];
return hooks; return hooks;
}); });
this.sequelize = options.sequelize;
this.underscored = this.underscored || this.underscoredAll; this.underscored = this.underscored || this.underscoredAll;
if (!this.options.tableName) { if (!this.options.tableName) {
...@@ -627,7 +755,6 @@ class Model { ...@@ -627,7 +755,6 @@ class Model {
return attribute; return attribute;
}); });
this.modelManager = modelManager;
this.primaryKeys = {}; this.primaryKeys = {};
// Setup names of timestamp attributes // Setup names of timestamp attributes
...@@ -668,6 +795,10 @@ class Model { ...@@ -668,6 +795,10 @@ class Model {
this.options.indexes = this.options.indexes.map(this._conformIndex); this.options.indexes = this.options.indexes.map(this._conformIndex);
this.sequelize.modelManager.addModel(this);
this.sequelize.runHooks('afterDefine', this);
return this; return this;
} }
...@@ -2045,7 +2176,7 @@ class Model { ...@@ -2045,7 +2176,7 @@ class Model {
const createdAtAttr = this._timestampAttributes.createdAt; const createdAtAttr = this._timestampAttributes.createdAt;
const updatedAtAttr = this._timestampAttributes.updatedAt; const updatedAtAttr = this._timestampAttributes.updatedAt;
const now = Utils.now(this.modelManager.sequelize.options.dialect); const now = Utils.now(this.sequelize.options.dialect);
let instances = records.map(values => this.build(values, {isNewRecord: true})); let instances = records.map(values => this.build(values, {isNewRecord: true}));
...@@ -2237,7 +2368,7 @@ class Model { ...@@ -2237,7 +2368,7 @@ class Model {
where[field] = deletedAtAttribute.hasOwnProperty('defaultValue') ? deletedAtAttribute.defaultValue : null; where[field] = deletedAtAttribute.hasOwnProperty('defaultValue') ? deletedAtAttribute.defaultValue : null;
attrValueHash[field] = Utils.now(this.modelManager.sequelize.options.dialect); attrValueHash[field] = Utils.now(this.sequelize.options.dialect);
return this.QueryInterface.bulkUpdate(this.getTableName(options), attrValueHash, _.defaults(where, options.where), options, this.rawAttributes); return this.QueryInterface.bulkUpdate(this.getTableName(options), attrValueHash, _.defaults(where, options.where), options, this.rawAttributes);
} else { } else {
return this.QueryInterface.bulkDelete(this.getTableName(options), options.where, options, this); return this.QueryInterface.bulkDelete(this.getTableName(options), options.where, options, this);
...@@ -2715,7 +2846,7 @@ class Model { ...@@ -2715,7 +2846,7 @@ class Model {
* @return {Sequelize} * @return {Sequelize}
*/ */
get sequelize() { get sequelize() {
return this.constructor.modelManager.sequelize; return this.constructor.sequelize;
} }
/** /**
......
...@@ -335,95 +335,21 @@ class Sequelize { ...@@ -335,95 +335,21 @@ class Sequelize {
* *
* @see {DataTypes} * @see {DataTypes}
* @see {Hooks} * @see {Hooks}
* @param {String} modelName The name of the model. The model will be stored in `sequelize.models` under this name * @param {String} modelName The name of the model. The model will be stored in `sequelize.models` under this name
* @param {Object} attributes An object, where each attribute is a column of the table. Each column can be either a DataType, a string or a type-description object, with the properties described below: * @param {Object} attributes An object, where each attribute is a column of the table. See Model.init()
* @param {String|DataType|Object} attributes.column The description of a database column * @param {Object} [options] These options are merged with the default define options provided to the Sequelize constructor and passed to Model.init()
* @param {String|DataType} attributes.column.type A string or a data type
* @param {Boolean} [attributes.column.allowNull=true] If false, the column will have a NOT NULL constraint, and a not null validation will be run before an instance is saved.
* @param {Any} [attributes.column.defaultValue=null] A literal default value, a JavaScript function, or an SQL function (see `sequelize.fn`)
* @param {String|Boolean} [attributes.column.unique=false] If true, the column will get a unique constraint. If a string is provided, the column will be part of a composite unique index. If multiple columns have the same string, they will be part of the same unique index
* @param {Boolean} [attributes.column.primaryKey=false]
* @param {String} [attributes.column.field=null] If set, sequelize will map the attribute name to a different name in the database
* @param {Boolean} [attributes.column.autoIncrement=false]
* @param {String} [attributes.column.comment=null]
* @param {String|Model} [attributes.column.references=null] An object with reference configurations
* @param {String|Model} [attributes.column.references.model] If this column references another table, provide it here as a Model, or a string
* @param {String} [attributes.column.references.key='id'] The column of the foreign table that this column references
* @param {String} [attributes.column.onUpdate] What should happen when the referenced key is updated. One of CASCADE, RESTRICT, SET DEFAULT, SET NULL or NO ACTION
* @param {String} [attributes.column.onDelete] What should happen when the referenced key is deleted. One of CASCADE, RESTRICT, SET DEFAULT, SET NULL or NO ACTION
* @param {Function} [attributes.column.get] Provide a custom getter for this column. Use `this.getDataValue(String)` to manipulate the underlying values.
* @param {Function} [attributes.column.set] Provide a custom setter for this column. Use `this.setDataValue(String, Value)` to manipulate the underlying values.
* @param {Object} [attributes.validate] An object of validations to execute for this column every time the model is saved. Can be either the name of a validation provided by validator.js, a validation function provided by extending validator.js (see the `DAOValidator` property for more details), or a custom validation function. Custom validation functions are called with the value of the field, and can possibly take a second callback argument, to signal that they are asynchronous. If the validator is sync, it should throw in the case of a failed validation, it it is async, the callback should be called with the error text.
* @param {Object} [options] These options are merged with the default define options provided to the Sequelize constructor
* @param {Object} [options.defaultScope={}] Define the default search scope to use for this model. Scopes have the same form as the options passed to find / findAll
* @param {Object} [options.scopes] More scopes, defined in the same way as defaultScope above. See `Model.scope` for more information about how scopes are defined, and what you can do with them
* @param {Boolean} [options.omitNull] Don't persist null values. This means that all columns with null values will not be saved
* @param {Boolean} [options.timestamps=true] Adds createdAt and updatedAt timestamps to the model.
* @param {Boolean} [options.paranoid=false] Calling `destroy` will not delete the model, but instead set a `deletedAt` timestamp if this is true. Needs `timestamps=true` to work
* @param {Boolean} [options.underscored=false] Converts all camelCased columns to underscored if true
* @param {Boolean} [options.underscoredAll=false] Converts camelCased model names to underscored table names if true
* @param {Boolean} [options.freezeTableName=false] If freezeTableName is true, sequelize will not try to alter the DAO name to get the table name. Otherwise, the model name will be pluralized
* @param {Object} [options.name] An object with two attributes, `singular` and `plural`, which are used when this model is associated to others.
* @param {String} [options.name.singular=Utils.singularize(modelName)]
* @param {String} [options.name.plural=Utils.pluralize(modelName)]
* @param {Array<Object>} [options.indexes]
* @param {String} [options.indexes[].name] The name of the index. Defaults to model name + _ + fields concatenated
* @param {String} [options.indexes[].type] Index type. Only used by mysql. One of `UNIQUE`, `FULLTEXT` and `SPATIAL`
* @param {String} [options.indexes[].method] The method to create the index by (`USING` statement in SQL). BTREE and HASH are supported by mysql and postgres, and postgres additionally supports GIST and GIN.
* @param {Boolean} [options.indexes[].unique=false] Should the index by unique? Can also be triggered by setting type to `UNIQUE`
* @param {Boolean} [options.indexes[].concurrently=false] PostgreSQL will build the index without taking any write locks. Postgres only
* @param {Array<String|Object>} [options.indexes[].fields] An array of the fields to index. Each field can either be a string containing the name of the field, a sequelize object (e.g `sequelize.fn`), or an object with the following attributes: `attribute` (field name), `length` (create a prefix index of length chars), `order` (the direction the column should be sorted in), `collate` (the collation (sort order) for the column)
* @param {String|Boolean} [options.createdAt] Override the name of the createdAt column if a string is provided, or disable it if false. Timestamps must be true. Not affected by underscored setting.
* @param {String|Boolean} [options.updatedAt] Override the name of the updatedAt column if a string is provided, or disable it if false. Timestamps must be true. Not affected by underscored setting.
* @param {String|Boolean} [options.deletedAt] Override the name of the deletedAt column if a string is provided, or disable it if false. Timestamps must be true. Not affected by underscored setting.
* @param {String} [options.tableName] Defaults to pluralized model name, unless freezeTableName is true, in which case it uses model name verbatim
* @param {String} [options.schema='public']
* @param {String} [options.engine]
* @param {String} [options.charset]
* @param {String} [options.comment]
* @param {String} [options.collate]
* @param {String} [options.initialAutoIncrement] Set the initial AUTO_INCREMENT value for the table in MySQL.
* @param {Object} [options.hooks] An object of hook function that are called before and after certain lifecycle events. The possible hooks are: beforeValidate, afterValidate, validationFailed, beforeBulkCreate, beforeBulkDestroy, beforeBulkUpdate, beforeCreate, beforeDestroy, beforeUpdate, afterCreate, afterDestroy, afterUpdate, afterBulkCreate, afterBulkDestory and afterBulkUpdate. See Hooks for more information about hook functions and their signatures. Each property can either be a function, or an array of functions.
* @param {Object} [options.validate] An object of model wide validations. Validations have access to all model values via `this`. If the validator function takes an argument, it is assumed to be async, and is called with a callback that accepts an optional error.
* *
* @return {Model} * @return {Model}
*/ */
define(modelName, attributes, options) { // testhint options:none define(modelName, attributes, options) { // testhint options:none
options = options || {}; options = options || {};
const globalOptions = this.options;
if (globalOptions.define) {
options = Utils.merge(globalOptions.define, options);
}
options = Utils.merge({
name: {
plural: Utils.pluralize(modelName),
singular: Utils.singularize(modelName)
},
indexes: [],
omitNul: globalOptions.omitNull
}, options);
// if you call "define" multiple times for the same modelName, do not clutter the factory
if (this.isDefined(modelName)) {
this.modelManager.removeModel(this.modelManager.getModel(modelName));
}
options.sequelize = this;
options.modelName = modelName; options.modelName = modelName;
this.runHooks('beforeDefine', attributes, options); options.sequelize = this;
modelName = options.modelName;
delete options.modelName;
const model = class extends Model {}; const model = class extends Model {};
Object.defineProperty(model, 'name', {value: modelName});
model.init(attributes, options, this.modelManager);
this.modelManager.addModel(model);
this.runHooks('afterDefine', model); model.init(attributes, options);
return model; return model;
} }
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!